From bfb7247eaed3f56958d02a6d738990f0160582a0 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 2 Aug 2022 12:08:36 +0200 Subject: [PATCH 001/383] Feature/template engine (#81) * feat: initial template engine feature * feat: initial merge feature * feat: fixed env/file replacement * feat: refactor merge * chore: wip walk logic * chore: fixed walk function * chore: merge tests and small refactoring * chore: refactoring of template engine * chore: upped go version; further refactoring * chore: refactoring * chore: template model and mapper refactoring * fix: skipping files with only whitespaces * feat: added --dry-run to template cmd [WIP] * chore: refactoring applyTemplates func * fix: template tests + added test on funcmap * fix: added licenses * chore: added tests to yaml, mapper * feat: added tests / refactoring on node_fields * fix: licenses * chore: moved function up * chore: refactor map to alias * chore: removed empty line * chore: refactoring template model * fix: broken test * fix: post review * chore: removed default from template suffix * fix: typo in last commit * feat: added filename in yaml parsing errors in template cmd * chore: work on change requests pt.1 Co-authored-by: Claudio Beatrice * chore: work on change requests - model_test Co-authored-by: Claudio Beatrice * chore: work on change requests pt.2 Co-authored-by: Claudio Beatrice * chore: added task to todo * fix: bugs after reverting to yamlv.2 * feat: added integration tests * fix: added licenses * fix: integration test * fix: integration test pt2 * fix: integration test pt3 * Update cmd/template.go Co-authored-by: Ramiro Algozino * fix: linting * fix: removed cmd banner on error * fix: removed double error * chore: missing NLs Co-authored-by: Claudio Beatrice * chore: add more furyctl template tests, fix some loggin and descriptions * fix: linting Co-authored-by: Luigi Barbato Co-authored-by: Claudio Beatrice Co-authored-by: Ramiro Algozino --- .drone.yml | 9 +- .goreleaser.yml | 2 +- Furyfile.yml | 2 +- LICENSE | 2 +- .../e2e-tests/aws-eks/bootstrap.tpl.yml | 2 +- .../e2e-tests/aws-eks/cluster.tpl.yml | 2 +- automated-tests/e2e-tests/aws-eks/tests.sh | 2 +- .../e2e-tests/gcp-gke/bootstrap.tpl.yml | 2 +- .../e2e-tests/gcp-gke/cluster.tpl.yml | 2 +- automated-tests/e2e-tests/gcp-gke/tests.sh | 2 +- .../e2e-tests/vsphere/cluster.tpl.yml | 2 +- .../e2e-tests/vsphere/tests-destroy.sh | 2 +- automated-tests/e2e-tests/vsphere/tests.sh | 2 +- .../integration/aws-eks/bootstrap.yml | 2 +- .../integration/aws-eks/cluster.yml | 2 +- .../integration/gcp-gke/bootstrap.yml | 2 +- .../integration/gcp-gke/cluster.yml | 2 +- .../integration/template-engine/.gitignore | 1 + .../data/expected-kustomization.yaml | 18 + .../complex-dry-run/distribution.yaml | 192 ++++++++++ .../test-data/complex-dry-run/furyctl.yaml | 325 ++++++++++++++++ .../source/config/example.yaml | 5 + .../source/kustomization.yaml.tpl | 28 ++ .../complex/data/expected-kustomization.yaml | 18 + .../test-data/complex/distribution.yaml | 192 ++++++++++ .../test-data/complex/furyctl.yaml | 325 ++++++++++++++++ .../complex/source/config/example.yaml | 5 + .../complex/source/kustomization.yaml.tpl | 28 ++ .../distribution.yaml | 5 + .../furyctl.yaml | 8 + .../source/file.txt.tpl | 1 + .../source/keepfile.txt | 1 + .../test-data/empty/source/file.txt.tpl | 1 + .../test-data/no-distribution-yaml/.gitkeep | 0 .../no-furyctl-yaml/distribution.yaml | 5 + .../simple-dry-run/distribution.yaml | 7 + .../test-data/simple-dry-run/furyctl.yaml | 8 + .../simple-dry-run/source/file.txt.tpl | 1 + .../simple-dry-run/source/keepfile.txt | 1 + .../test-data/simple/distribution.yaml | 7 + .../test-data/simple/furyctl.yaml | 8 + .../test-data/simple/source/file.txt.tpl | 1 + .../test-data/simple/source/keepfile.txt | 1 + .../integration/template-engine/tests.sh | 207 ++++++++++ .../integration/vsphere/cluster.yml | 2 +- cmd/bootstrap.go | 2 +- cmd/cluster.go | 2 +- cmd/completion.go | 2 +- cmd/init.go | 2 +- cmd/root.go | 2 +- cmd/selfprovision.go | 2 +- cmd/template.go | 121 ++++++ data/provisioners/bootstrap/aws/output.tf | 2 +- data/provisioners/bootstrap/aws/variables.tf | 2 +- data/provisioners/bootstrap/gcp/main.tf | 2 +- data/provisioners/bootstrap/gcp/output.tf | 2 +- data/provisioners/bootstrap/gcp/variables.tf | 2 +- data/provisioners/cluster/eks/output.tf | 2 +- data/provisioners/cluster/gke/main.tf | 2 +- data/provisioners/cluster/gke/output.tf | 2 +- data/provisioners/cluster/gke/variables.tf | 2 +- .../cluster/vsphere/furyagent/furyagent.yml | 2 +- data/provisioners/cluster/vsphere/main.tf | 2 +- data/provisioners/cluster/vsphere/output.tf | 2 +- .../cluster/vsphere/provision/all-in-one.yml | 2 +- .../provisioners/cluster/vsphere/variables.tf | 2 +- go.mod | 75 +++- go.sum | 54 +-- internal/bootstrap/bootstrap.go | 2 +- internal/bootstrap/configuration/aws.go | 2 +- internal/bootstrap/configuration/gcp.go | 2 +- .../bootstrap/provisioners/aws/provisioner.go | 2 +- .../bootstrap/provisioners/gcp/provisioner.go | 2 +- internal/cluster/cluster.go | 2 +- internal/cluster/configuration/common.go | 2 +- internal/cluster/configuration/gke.go | 2 +- internal/cluster/configuration/vsphere.go | 2 +- .../cluster/provisioners/gke/provisioner.go | 2 +- .../provisioners/vsphere/provisioner.go | 2 +- .../assets/aws-bootstrap-file-state.yml | 2 +- .../configuration/assets/aws-bootstrap.yml | 2 +- .../configuration/assets/invalid-config.yml | 2 +- .../configuration/assets/invalid-kind.yml | 2 +- .../assets/invalid-provisioner-bootstrap.yml | 2 +- internal/configuration/config.go | 2 +- internal/configuration/config_test.go | 2 +- internal/configuration/templates.go | 2 +- internal/io/fs.go | 95 +++++ internal/merge/merge.go | 58 +++ internal/merge/merge_test.go | 95 +++++ internal/merge/model.go | 78 ++++ internal/merge/model_test.go | 137 +++++++ internal/project/project.go | 2 +- internal/provisioners/provisioners.go | 2 +- internal/template/config.go | 18 + internal/template/funcmap.go | 27 ++ internal/template/funcmap_test.go | 39 ++ internal/template/generator.go | 150 ++++++++ internal/template/generator_test.go | 171 +++++++++ internal/template/mapper/mapper.go | 109 ++++++ internal/template/mapper/mapper_test.go | 98 +++++ internal/template/model.go | 218 +++++++++++ internal/template/model_test.go | 52 +++ internal/template/node.go | 151 ++++++++ internal/template/node_test.go | 359 ++++++++++++++++++ internal/yaml/yaml.go | 24 ++ internal/yaml/yaml_test.go | 47 +++ main.go | 2 +- pkg/analytics/analytics.go | 2 +- pkg/template/funcmap.go | 29 ++ pkg/terraform/install.go | 2 +- pkg/terraform/terraform.go | 2 +- pkg/utils/utils.go | 2 +- 113 files changed, 3645 insertions(+), 94 deletions(-) create mode 100644 automated-tests/integration/template-engine/.gitignore create mode 100644 automated-tests/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex-dry-run/distribution.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex-dry-run/furyctl.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl create mode 100644 automated-tests/integration/template-engine/test-data/complex/data/expected-kustomization.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex/distribution.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex/furyctl.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml create mode 100644 automated-tests/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl create mode 100644 automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml create mode 100644 automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml create mode 100644 automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl create mode 100644 automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt create mode 100644 automated-tests/integration/template-engine/test-data/empty/source/file.txt.tpl create mode 100644 automated-tests/integration/template-engine/test-data/no-distribution-yaml/.gitkeep create mode 100644 automated-tests/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml create mode 100644 automated-tests/integration/template-engine/test-data/simple-dry-run/distribution.yaml create mode 100644 automated-tests/integration/template-engine/test-data/simple-dry-run/furyctl.yaml create mode 100644 automated-tests/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl create mode 100644 automated-tests/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt create mode 100644 automated-tests/integration/template-engine/test-data/simple/distribution.yaml create mode 100644 automated-tests/integration/template-engine/test-data/simple/furyctl.yaml create mode 100644 automated-tests/integration/template-engine/test-data/simple/source/file.txt.tpl create mode 100644 automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt create mode 100644 automated-tests/integration/template-engine/tests.sh create mode 100644 cmd/template.go create mode 100644 internal/io/fs.go create mode 100644 internal/merge/merge.go create mode 100644 internal/merge/merge_test.go create mode 100644 internal/merge/model.go create mode 100644 internal/merge/model_test.go create mode 100644 internal/template/config.go create mode 100644 internal/template/funcmap.go create mode 100644 internal/template/funcmap_test.go create mode 100644 internal/template/generator.go create mode 100644 internal/template/generator_test.go create mode 100644 internal/template/mapper/mapper.go create mode 100644 internal/template/mapper/mapper_test.go create mode 100644 internal/template/model.go create mode 100644 internal/template/model_test.go create mode 100644 internal/template/node.go create mode 100644 internal/template/node_test.go create mode 100644 internal/yaml/yaml.go create mode 100644 internal/yaml/yaml_test.go create mode 100644 pkg/template/funcmap.go diff --git a/.drone.yml b/.drone.yml index df4bf56b6..883818dca 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. @@ -12,7 +12,7 @@ steps: pull: always commands: - go install github.com/google/addlicense@v1.0.0 - - addlicense -c "SIGHUP s.r.l" -v -l bsd --check . + - addlicense -c "SIGHUP s.r.l" -y 2017-present -v -l bsd --check . --- name: Build Test and Release @@ -95,6 +95,11 @@ steps: commands: - bats -t ./automated-tests/integration/vsphere/tests.sh + - <<: *integration + name: integration-template-engine + commands: + - bats -t ./automated-tests/integration/template-engine/tests.sh + - &e2e name: e2e-gcp image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.3.0_v1.24.9_20.04 diff --git a/.goreleaser.yml b/.goreleaser.yml index d08defcaf..7d2f6dc55 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/Furyfile.yml b/Furyfile.yml index ae36a9d03..bfda94c5c 100644 --- a/Furyfile.yml +++ b/Furyfile.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/LICENSE b/LICENSE index 7d7e5ecf0..6cdf36d03 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright © 2018 Sighup SRL support@sighup.io + Copyright © 2017-present SIGHUP SRL support@sighup.io Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml b/automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml index c5006b332..5b529912f 100644 --- a/automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml +++ b/automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/aws-eks/cluster.tpl.yml b/automated-tests/e2e-tests/aws-eks/cluster.tpl.yml index 7fcc50c7a..7deaa066c 100644 --- a/automated-tests/e2e-tests/aws-eks/cluster.tpl.yml +++ b/automated-tests/e2e-tests/aws-eks/cluster.tpl.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/aws-eks/tests.sh b/automated-tests/e2e-tests/aws-eks/tests.sh index 28d94992f..19b81787b 100644 --- a/automated-tests/e2e-tests/aws-eks/tests.sh +++ b/automated-tests/e2e-tests/aws-eks/tests.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bats -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml b/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml index 3274df410..a87c11560 100644 --- a/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml +++ b/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml b/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml index d956ba98a..a878fb6b5 100644 --- a/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml +++ b/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/gcp-gke/tests.sh b/automated-tests/e2e-tests/gcp-gke/tests.sh index 11b0234bb..faa6615ea 100644 --- a/automated-tests/e2e-tests/gcp-gke/tests.sh +++ b/automated-tests/e2e-tests/gcp-gke/tests.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bats -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/vsphere/cluster.tpl.yml b/automated-tests/e2e-tests/vsphere/cluster.tpl.yml index c44ec23b2..9fbc61fc0 100644 --- a/automated-tests/e2e-tests/vsphere/cluster.tpl.yml +++ b/automated-tests/e2e-tests/vsphere/cluster.tpl.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/vsphere/tests-destroy.sh b/automated-tests/e2e-tests/vsphere/tests-destroy.sh index cb70f17f4..87c972700 100644 --- a/automated-tests/e2e-tests/vsphere/tests-destroy.sh +++ b/automated-tests/e2e-tests/vsphere/tests-destroy.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bats -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/e2e-tests/vsphere/tests.sh b/automated-tests/e2e-tests/vsphere/tests.sh index 447460a40..9ef201a05 100644 --- a/automated-tests/e2e-tests/vsphere/tests.sh +++ b/automated-tests/e2e-tests/vsphere/tests.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bats -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/integration/aws-eks/bootstrap.yml b/automated-tests/integration/aws-eks/bootstrap.yml index 66829a7fc..14fd0f495 100644 --- a/automated-tests/integration/aws-eks/bootstrap.yml +++ b/automated-tests/integration/aws-eks/bootstrap.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/integration/aws-eks/cluster.yml b/automated-tests/integration/aws-eks/cluster.yml index 57443b137..765c03283 100644 --- a/automated-tests/integration/aws-eks/cluster.yml +++ b/automated-tests/integration/aws-eks/cluster.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/integration/gcp-gke/bootstrap.yml b/automated-tests/integration/gcp-gke/bootstrap.yml index f1a9c1569..af0a7b7c7 100644 --- a/automated-tests/integration/gcp-gke/bootstrap.yml +++ b/automated-tests/integration/gcp-gke/bootstrap.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/integration/gcp-gke/cluster.yml b/automated-tests/integration/gcp-gke/cluster.yml index fcf337013..689bf3818 100644 --- a/automated-tests/integration/gcp-gke/cluster.yml +++ b/automated-tests/integration/gcp-gke/cluster.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/automated-tests/integration/template-engine/.gitignore b/automated-tests/integration/template-engine/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/automated-tests/integration/template-engine/.gitignore @@ -0,0 +1 @@ +target diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml b/automated-tests/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml new file mode 100644 index 000000000..3e239a498 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../vendor/katalog/ingress/cert-manager + - ../../vendor/katalog/ingress/nginx + - ../../vendor/katalog/ingress/forecastle + - resources/cert-manager-clusterissuer.yml + +patchesStrategicMerge: + - patches/cert-manager.yml + - patches/infra-nodes.yml + - patches/ingress-nginx.yml diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/distribution.yaml b/automated-tests/integration/template-engine/test-data/complex-dry-run/distribution.yaml new file mode 100644 index 000000000..905a6867d --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex-dry-run/distribution.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789123:role/clustername-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: {} + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: {} + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role + region: eu-west-1 + bucket: velero-bucket + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: {} + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: 5n12jUeGL5Oy9zXiCOP929xc4sG2n2/CB9QTo2piNsU= + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: ausahceemoh3ahGhiu6aiNguothuVakuYah5Lie5 + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: xI1QpxLnGwreSJhnXRWNSthsdVLv6aZFX+Cwos5SCvY= + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ion-eks-demo + iamRoleArn: arn:aws:iam::363601582189:role/demo-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/furyctl.yaml b/automated-tests/integration/template-engine/test-data/complex-dry-run/furyctl.yaml new file mode 100644 index 000000000..c200caab5 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex-dry-run/furyctl.yaml @@ -0,0 +1,325 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + # the name will be used as a prefix/suffix for all the managed resources + name: awesome-cluster-staging +spec: + # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions + distributionVersion: "v1.24.7" + # Under the hood, furyctl uses other tools like terraform, kustomize, etc + toolsConfiguration: + terraform: + state: + backend: s3 + config: + bucket: awesome-bucket-created-outside-furyctl + # changed from key, because each terraform project state will be placed in the directory defined by the prefix + keyPrefix: furyctl/ + region: eu-west-1 + # tags to apply to all resources in AWS + tags: + env: "staging" + k8s: "awesome" + # whathever: "something" + infrastructure: + vpc: + # the bootstrap phase can be enabled or disabled + enabled: true + # new network configuration, with a more hierarchical structure + network: + cidr: 10.1.0.0/16 + subnetsCidrs: + private: + - 10.1.0.0/20 + - 10.1.16.0/20 + - 10.1.32.0/20 + public: + - 10.1.48.0/24 + - 10.1.49.0/24 + - 10.1.50.0/24 + vpn: + # the vpn creation can be optional + enabled: true + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed56778 XYX" + - {file://relative/path/to/ssh.pub} + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 + cluster: + # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase + # and will be automatically gathered from the output of the bootstrap phase + vpcId: vpc-0f92da9b4a2089963 + # optional, see the above comment + subnetIds: + - subnet-0ab84702287e38ccb + - subnet-0ae4e9199d9192226 + - subnet-01787e8da51e4f070 + # cidr allowed to talk with the apiServer + apiServerAllowedCidrs: + - 10.1.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + # optional, was OS, and version was removed + ami: + id: null + owner: null + # sizing, with a more hierarchical structure + size: + min: 1 + max: 3 + instance: + type: t3.micro + # optional, this enable spot instances on the ASG + spot: false + volumeSize: 50 + # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + # labels that are added to the nodes + labels: + nodepool: worker + node.kubernetes.io/role: worker + # optional, taints added to the nodes + taints: + - node.kubernetes.io/role=worker:NoSchedule + # tags added to the ASG + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + # additional rules added to the ASG nodes security group + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + # aws-auth configmap definiton + awsAuth: + # Additional AWS account id to add to the aws-auth configmap, optional + additionalAccounts: + - "777777777777" + - "88888888888" + # Additional users to add to the aws-auth configmap, optional + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + # Additional roles to add to the aws-auth configmap, optional + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules + distribution: + common: + # relativeVendorPath: "../../vendor" should be automatically set by furyctl + # provider: + # type: eks automatically set by furyctl + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + # the base ingress domain for all the ingresses in the cluster + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + # secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + # cert: {file://ssl.crt} + # key: {file://ssl.key} + # ca: {file://ssl.ca} + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, should be taken automatically from the role generation using module.ingress.dns.public settings + # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns + dns: + public: + enabled: true + name: "fury-demo.sighup.io" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + private: + enabled: true + name: "internal.fury-demo.sighup.io" + # optional, if vpc is enabled: false + vpcId: "vpc123123123123" + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: {} + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: {} + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: {} + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: {env://KFD_BASIC_AUTH_PASSWORD} + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: {env://KFD_AUTH_POMERIUM_COOKIE_SECRET} + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: {env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET} + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: {env://KFD_AUTH_POMERIUM_SHARED_SECRET} + dex: + # see dex documentation for more information + connectors: + - type: github + # Required field for connector id. + id: github + # Required field for connector name. + name: GitHub + config: + # Credentials can be string literals or pulled from the environment. + clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} + clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + redirectURI: https://login.fury-demo.sighup.io/callback + loadAllGroups: false + teamNameField: slug + useLoginAsID: false diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml b/automated-tests/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml new file mode 100644 index 000000000..24e987929 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml @@ -0,0 +1,5 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +configdata: example diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl b/automated-tests/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl new file mode 100644 index 000000000..19922bd2b --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl @@ -0,0 +1,28 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +{{ if .modules.ingress }} +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ .common.relativeVendorPath }}/katalog/ingress/cert-manager +{{- if eq .modules.ingress.nginx.type "dual" }} + - {{ .common.relativeVendorPath }}/katalog/ingress/dual-nginx +{{- end }} +{{- if eq .modules.ingress.nginx.type "single" }} + - {{ .common.relativeVendorPath }}/katalog/ingress/nginx +{{- end }} + - {{ .common.relativeVendorPath }}/katalog/ingress/forecastle +{{- if .modules.ingress.certManager.clusterIssuer.notExistingProperty }} + - resources/cert-manager-clusterissuer.yml +{{- end }} + +patchesStrategicMerge: +{{- if .modules.ingress.certManager.clusterIssuer.notExistingProperty }} + - patches/cert-manager.yml +{{- end }} + - patches/infra-nodes.yml + - patches/ingress-nginx.yml +{{- end }} diff --git a/automated-tests/integration/template-engine/test-data/complex/data/expected-kustomization.yaml b/automated-tests/integration/template-engine/test-data/complex/data/expected-kustomization.yaml new file mode 100644 index 000000000..3e239a498 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex/data/expected-kustomization.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../vendor/katalog/ingress/cert-manager + - ../../vendor/katalog/ingress/nginx + - ../../vendor/katalog/ingress/forecastle + - resources/cert-manager-clusterissuer.yml + +patchesStrategicMerge: + - patches/cert-manager.yml + - patches/infra-nodes.yml + - patches/ingress-nginx.yml diff --git a/automated-tests/integration/template-engine/test-data/complex/distribution.yaml b/automated-tests/integration/template-engine/test-data/complex/distribution.yaml new file mode 100644 index 000000000..905a6867d --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex/distribution.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789123:role/clustername-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: {} + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: {} + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role + region: eu-west-1 + bucket: velero-bucket + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: {} + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: 5n12jUeGL5Oy9zXiCOP929xc4sG2n2/CB9QTo2piNsU= + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: ausahceemoh3ahGhiu6aiNguothuVakuYah5Lie5 + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: xI1QpxLnGwreSJhnXRWNSthsdVLv6aZFX+Cwos5SCvY= + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ion-eks-demo + iamRoleArn: arn:aws:iam::363601582189:role/demo-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/automated-tests/integration/template-engine/test-data/complex/furyctl.yaml b/automated-tests/integration/template-engine/test-data/complex/furyctl.yaml new file mode 100644 index 000000000..c200caab5 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex/furyctl.yaml @@ -0,0 +1,325 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + # the name will be used as a prefix/suffix for all the managed resources + name: awesome-cluster-staging +spec: + # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions + distributionVersion: "v1.24.7" + # Under the hood, furyctl uses other tools like terraform, kustomize, etc + toolsConfiguration: + terraform: + state: + backend: s3 + config: + bucket: awesome-bucket-created-outside-furyctl + # changed from key, because each terraform project state will be placed in the directory defined by the prefix + keyPrefix: furyctl/ + region: eu-west-1 + # tags to apply to all resources in AWS + tags: + env: "staging" + k8s: "awesome" + # whathever: "something" + infrastructure: + vpc: + # the bootstrap phase can be enabled or disabled + enabled: true + # new network configuration, with a more hierarchical structure + network: + cidr: 10.1.0.0/16 + subnetsCidrs: + private: + - 10.1.0.0/20 + - 10.1.16.0/20 + - 10.1.32.0/20 + public: + - 10.1.48.0/24 + - 10.1.49.0/24 + - 10.1.50.0/24 + vpn: + # the vpn creation can be optional + enabled: true + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed56778 XYX" + - {file://relative/path/to/ssh.pub} + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 + cluster: + # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase + # and will be automatically gathered from the output of the bootstrap phase + vpcId: vpc-0f92da9b4a2089963 + # optional, see the above comment + subnetIds: + - subnet-0ab84702287e38ccb + - subnet-0ae4e9199d9192226 + - subnet-01787e8da51e4f070 + # cidr allowed to talk with the apiServer + apiServerAllowedCidrs: + - 10.1.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + # optional, was OS, and version was removed + ami: + id: null + owner: null + # sizing, with a more hierarchical structure + size: + min: 1 + max: 3 + instance: + type: t3.micro + # optional, this enable spot instances on the ASG + spot: false + volumeSize: 50 + # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + # labels that are added to the nodes + labels: + nodepool: worker + node.kubernetes.io/role: worker + # optional, taints added to the nodes + taints: + - node.kubernetes.io/role=worker:NoSchedule + # tags added to the ASG + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + # additional rules added to the ASG nodes security group + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + # aws-auth configmap definiton + awsAuth: + # Additional AWS account id to add to the aws-auth configmap, optional + additionalAccounts: + - "777777777777" + - "88888888888" + # Additional users to add to the aws-auth configmap, optional + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + # Additional roles to add to the aws-auth configmap, optional + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules + distribution: + common: + # relativeVendorPath: "../../vendor" should be automatically set by furyctl + # provider: + # type: eks automatically set by furyctl + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + # the base ingress domain for all the ingresses in the cluster + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + # secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + # cert: {file://ssl.crt} + # key: {file://ssl.key} + # ca: {file://ssl.ca} + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, should be taken automatically from the role generation using module.ingress.dns.public settings + # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns + dns: + public: + enabled: true + name: "fury-demo.sighup.io" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + private: + enabled: true + name: "internal.fury-demo.sighup.io" + # optional, if vpc is enabled: false + vpcId: "vpc123123123123" + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: {} + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: {} + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: {} + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: {env://KFD_BASIC_AUTH_PASSWORD} + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: {env://KFD_AUTH_POMERIUM_COOKIE_SECRET} + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: {env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET} + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: {env://KFD_AUTH_POMERIUM_SHARED_SECRET} + dex: + # see dex documentation for more information + connectors: + - type: github + # Required field for connector id. + id: github + # Required field for connector name. + name: GitHub + config: + # Credentials can be string literals or pulled from the environment. + clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} + clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + redirectURI: https://login.fury-demo.sighup.io/callback + loadAllGroups: false + teamNameField: slug + useLoginAsID: false diff --git a/automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml b/automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml new file mode 100644 index 000000000..24e987929 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml @@ -0,0 +1,5 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +configdata: example diff --git a/automated-tests/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl b/automated-tests/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl new file mode 100644 index 000000000..283a59c7d --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl @@ -0,0 +1,28 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +{{ if .modules.ingress }} +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ .common.relativeVendorPath }}/katalog/ingress/cert-manager +{{- if eq .modules.ingress.nginx.type "dual" }} + - {{ .common.relativeVendorPath }}/katalog/ingress/dual-nginx +{{- end }} +{{- if eq .modules.ingress.nginx.type "single" }} + - {{ .common.relativeVendorPath }}/katalog/ingress/nginx +{{- end }} + - {{ .common.relativeVendorPath }}/katalog/ingress/forecastle +{{- if .modules.ingress.certManager.clusterIssuer.name }} + - resources/cert-manager-clusterissuer.yml +{{- end }} + +patchesStrategicMerge: +{{- if .modules.ingress.certManager.clusterIssuer.name }} + - patches/cert-manager.yml +{{- end }} + - patches/infra-nodes.yml + - patches/ingress-nginx.yml +{{- end }} diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml new file mode 100644 index 000000000..0a5713f36 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml @@ -0,0 +1,5 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +wrong: {} diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml new file mode 100644 index 000000000..246e7604f --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +spec: + distribution: + test: + hello: testValue diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl new file mode 100644 index 000000000..80f72d16b --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl @@ -0,0 +1 @@ +{{.test.hello}} diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt new file mode 100644 index 000000000..30d74d258 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/automated-tests/integration/template-engine/test-data/empty/source/file.txt.tpl b/automated-tests/integration/template-engine/test-data/empty/source/file.txt.tpl new file mode 100644 index 000000000..557db03de --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/empty/source/file.txt.tpl @@ -0,0 +1 @@ +Hello World diff --git a/automated-tests/integration/template-engine/test-data/no-distribution-yaml/.gitkeep b/automated-tests/integration/template-engine/test-data/no-distribution-yaml/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/automated-tests/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml b/automated-tests/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml new file mode 100644 index 000000000..ce9e8b004 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml @@ -0,0 +1,5 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: {} diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/distribution.yaml b/automated-tests/integration/template-engine/test-data/simple-dry-run/distribution.yaml new file mode 100644 index 000000000..493b3eb66 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple-dry-run/distribution.yaml @@ -0,0 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + test: + hello: defaultValue diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/furyctl.yaml b/automated-tests/integration/template-engine/test-data/simple-dry-run/furyctl.yaml new file mode 100644 index 000000000..246e7604f --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple-dry-run/furyctl.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +spec: + distribution: + test: + hello: testValue diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl b/automated-tests/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl new file mode 100644 index 000000000..80f72d16b --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl @@ -0,0 +1 @@ +{{.test.hello}} diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt b/automated-tests/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt new file mode 100644 index 000000000..30d74d258 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/automated-tests/integration/template-engine/test-data/simple/distribution.yaml b/automated-tests/integration/template-engine/test-data/simple/distribution.yaml new file mode 100644 index 000000000..493b3eb66 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple/distribution.yaml @@ -0,0 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + test: + hello: defaultValue diff --git a/automated-tests/integration/template-engine/test-data/simple/furyctl.yaml b/automated-tests/integration/template-engine/test-data/simple/furyctl.yaml new file mode 100644 index 000000000..246e7604f --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple/furyctl.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +spec: + distribution: + test: + hello: testValue diff --git a/automated-tests/integration/template-engine/test-data/simple/source/file.txt.tpl b/automated-tests/integration/template-engine/test-data/simple/source/file.txt.tpl new file mode 100644 index 000000000..80f72d16b --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple/source/file.txt.tpl @@ -0,0 +1 @@ +{{.test.hello}} diff --git a/automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt b/automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt new file mode 100644 index 000000000..30d74d258 --- /dev/null +++ b/automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/automated-tests/integration/template-engine/tests.sh b/automated-tests/integration/template-engine/tests.sh new file mode 100644 index 000000000..1a3b0f010 --- /dev/null +++ b/automated-tests/integration/template-engine/tests.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bats +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + + +load "./../../helper" + +OS="linux" +if [[ "${OSTYPE}" == "darwin"* ]]; then + OS="darwin" +fi +CPUARCH="amd64_v1" +if [ "$(uname -m)" = "arm64" ]; then + CPUARCH="arm64" +fi + +@test "furyctl" { + info + init(){ + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl version + } + run init + if [[ ${status} -ne 0 ]]; then + echo "${output}" >&3 + fi + [ "${status}" -eq 0 ] +} + +@test "no distribution file" { + info + test_dir="./automated-tests/integration/template-engine/test-data/no-distribution-yaml" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template + } + run init + + [ "${status}" -eq 1 ] + + if [[ ${output} != *"distribution.yaml: no such file or directory"* ]]; then + echo "${output}" >&3 + fi + [[ "${output}" == *"distribution.yaml: no such file or directory"* ]] +} + +@test "no furyctl.yaml file" { + info + test_dir="./automated-tests/integration/template-engine/test-data/no-furyctl-yaml" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template + } + run init + + [ "${status}" -eq 1 ] + + if [[ ${output} != *"furyctl.yaml: no such file or directory"* ]]; then + echo "${output}" >&3 + fi + [[ "${output}" == *"furyctl.yaml: no such file or directory"* ]] +} + +@test "no data property in distribution.yaml file" { + info + test_dir="./automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template + } + run init + + [ "${status}" -eq 1 ] + + if [[ ${output} != *"incorrect base file, cannot access key data on map"* ]]; then + echo "${output}" >&3 + fi + [[ "${output}" == *"incorrect base file, cannot access key data on map"* ]] +} + +@test "empty template" { + info + test_dir="./automated-tests/integration/template-engine/test-data/empty" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template + if [ -f ./target/file.txt ]; then false; else true; fi + } + run init + + if [[ ${status} -ne 0 ]]; then + echo "${output}" >&3 + fi + [ "${status}" -eq 0 ] +} + +@test "simple template dry-run" { + info + test_dir="./automated-tests/integration/template-engine/test-data/simple-dry-run" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template --dry-run + cat ./target/file.txt | grep "testValue" + } + run init + + if [[ ${status} -ne 0 ]]; then + echo "${output}" >&3 + fi + [ "${status}" -eq 0 ] +} + +@test "simple template" { + info + test_dir="./automated-tests/integration/template-engine/test-data/simple" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template + cat ./target/file.txt | grep "testValue" + } + run init + + if [[ ${status} -ne 0 ]]; then + echo "${output}" >&3 + fi + [ "${status}" -eq 0 ] +} + +@test "complex template dry-run" { + info + test_dir="./automated-tests/integration/template-engine/test-data/complex" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template --dry-run + + # test that the config/example.yaml file has been generated + if [ ! -f ./target/config/example.yaml ]; then + echo "config/example.yaml file not generated" >&3 + false + fi + + # test that the kustomization.yaml file has been generated + if [ ! -f ./target/kustomization.yaml ]; then + echo "kustomization.yaml file not generated" >&3 + false + fi + + # test that the config/example.yaml contains the string "configdata: example" + if ! grep -q "configdata: example" ./target/config/example.yaml; then + echo "config/example.yaml file does not contain the string 'configdata: example'" >&3 + false + fi + + # test that the kustomization.yaml contains the same data of data/expected-kustomization.yaml + if ! diff -q ./target/kustomization.yaml ./data/expected-kustomization.yaml; then + echo -e "kustomization.yaml file does not contain the same data of data/expected-kustomization.yaml, Diff:" >&3 + diff ./target/kustomization.yaml ./data/expected-kustomization.yaml >&3 + false + fi + } + run init + + if [[ ${status} -ne 0 ]]; then + echo "${output}" >&3 + fi + [ "${status}" -eq 0 ] +} + +@test "complex template" { + info + test_dir="./automated-tests/integration/template-engine/test-data/complex" + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug template + + # test that the config/example.yaml file has been generated + if [ ! -f ./target/config/example.yaml ]; then + echo "config/example.yaml file not generated" >&3 + false + fi + + # test that the kustomization.yaml file has been generated + if [ ! -f ./target/kustomization.yaml ]; then + echo "kustomization.yaml file not generated" >&3 + false + fi + + # test that the config/example.yaml contains the string "configdata: example" + if ! grep -q "configdata: example" ./target/config/example.yaml; then + echo "config/example.yaml file does not contain the string 'configdata: example'" >&3 + false + fi + + # test that the kustomization.yaml contains the same data of data/expected-kustomization.yaml + if ! diff -q ./target/kustomization.yaml ./data/expected-kustomization.yaml; then + echo -e "kustomization.yaml file does not contain the same data of data/expected-kustomization.yaml, Diff:" >&3 + diff ./target/kustomization.yaml ./data/expected-kustomization.yaml >&3 + false + fi + } + run init + + if [[ ${status} -ne 0 ]]; then + echo "${output}" >&3 + fi + [ "${status}" -eq 0 ] +} diff --git a/automated-tests/integration/vsphere/cluster.yml b/automated-tests/integration/vsphere/cluster.yml index 8d3715f19..62c9e1aaa 100644 --- a/automated-tests/integration/vsphere/cluster.yml +++ b/automated-tests/integration/vsphere/cluster.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index f2058cf39..97b9bf21e 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/cmd/cluster.go b/cmd/cluster.go index 0277ec1c0..fb540a237 100644 --- a/cmd/cluster.go +++ b/cmd/cluster.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/cmd/completion.go b/cmd/completion.go index 33e0e7a57..a88e266ba 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/cmd/init.go b/cmd/init.go index 188ad070f..5140201bd 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/cmd/root.go b/cmd/root.go index 65f6c7842..bb872dd59 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/cmd/selfprovision.go b/cmd/selfprovision.go index c7f87365a..c43b77be8 100644 --- a/cmd/selfprovision.go +++ b/cmd/selfprovision.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/cmd/template.go b/cmd/template.go new file mode 100644 index 000000000..17a9b455b --- /dev/null +++ b/cmd/template.go @@ -0,0 +1,121 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/sighupio/furyctl/internal/merge" + yaml2 "github.com/sighupio/furyctl/internal/yaml" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + "github.com/sighupio/furyctl/internal/template" +) + +var ( + tDryRun bool + tNoOverwrite bool + + TemplateCmd = &cobra.Command{ + Use: "template", + Short: "Renders the distribution's manifests from a template and a configuration file", + Long: `Generates a folder with the Kustomization project for deploying Kubernetes Fury Distribution into a cluster. +The generated folder will be created starting from a provided template and the parameters set in a configuration file that is merged with default values.`, + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + + //TODO(rm-2470): To be reworked in redmine task - Define template command flags. + source := "source" + target := "target" + suffix := ".tpl" + distributionFilePath := "distribution.yaml" + furyctlFilePath := "furyctl.yaml" + + distributionFile, err := yaml2.FromFile[map[any]any](distributionFilePath) + if err != nil { + return fmt.Errorf("%s - %w", distributionFilePath, err) + } + + furyctlFile, err := yaml2.FromFile[map[any]any](furyctlFilePath) + if err != nil { + return fmt.Errorf("%s - %w", furyctlFilePath, err) + } + + if _, err := os.Stat(source); os.IsNotExist(err) { + return fmt.Errorf("source directory does not exist") + } + + merger := merge.NewMerger( + merge.NewDefaultModel(distributionFile, ".data"), + merge.NewDefaultModel(furyctlFile, ".spec.distribution"), + ) + + mergedDistribution, err := merger.Merge() + if err != nil { + return err + } + + outYaml, err := yaml.Marshal(mergedDistribution) + if err != nil { + return err + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-") + if err != nil { + return err + } + + confPath := filepath.Join(outDirPath, "config.yaml") + + logrus.Debugf("config path = %s", confPath) + + if err = os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return err + } + + if !tNoOverwrite { + if err = os.RemoveAll(target); err != nil { + return err + } + } + + templateModel, err := template.NewTemplateModel( + source, + target, + confPath, + outDirPath, + suffix, + tNoOverwrite, + tDryRun, + ) + if err != nil { + return err + } + + return templateModel.Generate() + }, + } +) + +func init() { + rootCmd.AddCommand(TemplateCmd) + TemplateCmd.Flags().BoolVar( + &tDryRun, + "dry-run", + false, + "Furyctl will try its best to generate the manifests despite the errors", + ) + TemplateCmd.Flags().BoolVar( + &tNoOverwrite, + "no-overwrite", + false, + "Stop if target directory is not empty", + ) +} diff --git a/data/provisioners/bootstrap/aws/output.tf b/data/provisioners/bootstrap/aws/output.tf index b562b7cd2..2a7f45994 100644 --- a/data/provisioners/bootstrap/aws/output.tf +++ b/data/provisioners/bootstrap/aws/output.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/bootstrap/aws/variables.tf b/data/provisioners/bootstrap/aws/variables.tf index fc5c5af8f..1f04ecc4a 100644 --- a/data/provisioners/bootstrap/aws/variables.tf +++ b/data/provisioners/bootstrap/aws/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/bootstrap/gcp/main.tf b/data/provisioners/bootstrap/gcp/main.tf index d09d62d9c..7e4ae41cb 100644 --- a/data/provisioners/bootstrap/gcp/main.tf +++ b/data/provisioners/bootstrap/gcp/main.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/bootstrap/gcp/output.tf b/data/provisioners/bootstrap/gcp/output.tf index 71210d1a4..0d8567706 100644 --- a/data/provisioners/bootstrap/gcp/output.tf +++ b/data/provisioners/bootstrap/gcp/output.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/bootstrap/gcp/variables.tf b/data/provisioners/bootstrap/gcp/variables.tf index 3afadae74..87696c0c2 100644 --- a/data/provisioners/bootstrap/gcp/variables.tf +++ b/data/provisioners/bootstrap/gcp/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/eks/output.tf b/data/provisioners/cluster/eks/output.tf index d0f95729c..22e323830 100644 --- a/data/provisioners/cluster/eks/output.tf +++ b/data/provisioners/cluster/eks/output.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/gke/main.tf b/data/provisioners/cluster/gke/main.tf index 0f5f48ccc..e5818c928 100644 --- a/data/provisioners/cluster/gke/main.tf +++ b/data/provisioners/cluster/gke/main.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/gke/output.tf b/data/provisioners/cluster/gke/output.tf index d47ec2c1b..187f2fe57 100644 --- a/data/provisioners/cluster/gke/output.tf +++ b/data/provisioners/cluster/gke/output.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/gke/variables.tf b/data/provisioners/cluster/gke/variables.tf index 1ab8b7f7a..375c6637e 100644 --- a/data/provisioners/cluster/gke/variables.tf +++ b/data/provisioners/cluster/gke/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/vsphere/furyagent/furyagent.yml b/data/provisioners/cluster/vsphere/furyagent/furyagent.yml index 2b9ebab11..a92610069 100644 --- a/data/provisioners/cluster/vsphere/furyagent/furyagent.yml +++ b/data/provisioners/cluster/vsphere/furyagent/furyagent.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/data/provisioners/cluster/vsphere/main.tf b/data/provisioners/cluster/vsphere/main.tf index 93cbdda4d..65c15f54b 100644 --- a/data/provisioners/cluster/vsphere/main.tf +++ b/data/provisioners/cluster/vsphere/main.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/vsphere/output.tf b/data/provisioners/cluster/vsphere/output.tf index c7c0ce573..fc3743862 100644 --- a/data/provisioners/cluster/vsphere/output.tf +++ b/data/provisioners/cluster/vsphere/output.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/data/provisioners/cluster/vsphere/provision/all-in-one.yml b/data/provisioners/cluster/vsphere/provision/all-in-one.yml index 13646ba00..f212b1ecf 100644 --- a/data/provisioners/cluster/vsphere/provision/all-in-one.yml +++ b/data/provisioners/cluster/vsphere/provision/all-in-one.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/data/provisioners/cluster/vsphere/variables.tf b/data/provisioners/cluster/vsphere/variables.tf index e5da15795..0d77c4bd5 100644 --- a/data/provisioners/cluster/vsphere/variables.tf +++ b/data/provisioners/cluster/vsphere/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ diff --git a/go.mod b/go.mod index b27c641d3..20a335e49 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/sighupio/furyctl require ( - github.com/aws/aws-sdk-go v1.27.0 // indirect github.com/briandowns/spinner v1.12.0 github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v0.0.0-20180925151559-f8d5594f958e @@ -15,14 +14,86 @@ require ( github.com/hashicorp/terraform-exec v0.13.3 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/karrick/godirwalk v1.17.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 github.com/relex/aini v1.2.1 github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.8.1 + github.com/stretchr/testify v1.7.0 golang.org/x/term v0.5.0 // indirect golang.org/x/tools v0.5.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) -go 1.13 +require ( + cloud.google.com/go v0.81.0 // indirect + cloud.google.com/go/storage v1.10.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/aws/aws-sdk-go v1.27.0 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.10.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gobuffalo/logger v1.0.6 // indirect + github.com/gobuffalo/packd v1.0.1 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-uuid v1.0.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/terraform-json v0.10.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/jstemmer/go-junit-report v0.9.1 // indirect + github.com/karrick/godirwalk v1.17.0 // indirect + github.com/klauspost/compress v1.11.2 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/markbates/errx v1.1.0 // indirect + github.com/markbates/oncer v1.0.0 // indirect + github.com/markbates/safe v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/ulikunitz/xz v0.5.8 // indirect + github.com/zclconf/go-cty v1.8.2 // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect + golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect + golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/api v0.44.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect + google.golang.org/grpc v1.38.0 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +go 1.18 diff --git a/go.sum b/go.sum index 6f283dcbe..d099cefbe 100644 --- a/go.sum +++ b/go.sum @@ -42,15 +42,19 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= @@ -58,7 +62,6 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkE github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= @@ -82,6 +85,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -102,19 +106,16 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.1.0 h1:4pl5BV4o7ZG/lterP4S6WzJ6xr49Ba5ET9ygheTYahk= github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= @@ -197,6 +198,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= @@ -243,6 +246,8 @@ github.com/hashicorp/terraform-exec v0.13.3 h1:R6L2mNpDGSEqtLrSONN8Xth0xYwNrnEVz github.com/hashicorp/terraform-exec v0.13.3/go.mod h1:SSg6lbUsVB3DmFyCPjBPklqf6EYGX0TlQ6QTxOlikDU= github.com/hashicorp/terraform-json v0.10.0 h1:9syPD/Y5t+3uFjG8AiWVPu1bklJD8QB8iTCaJASc8oQ= github.com/hashicorp/terraform-json v0.10.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -305,6 +310,7 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -317,6 +323,7 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -327,7 +334,6 @@ github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5d github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -348,11 +354,15 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -362,8 +372,8 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -397,7 +407,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.2 h1:u+xZfBKgpycDnTNjPhGiTEYZS5qS/Sb5MqSfm7vzcjg= @@ -424,6 +433,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -465,9 +475,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -507,9 +516,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -534,9 +542,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -588,18 +594,13 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -666,9 +667,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index f6f8e1806..4687ecaca 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/bootstrap/configuration/aws.go b/internal/bootstrap/configuration/aws.go index c41dfd8b8..de492e98e 100644 --- a/internal/bootstrap/configuration/aws.go +++ b/internal/bootstrap/configuration/aws.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/bootstrap/configuration/gcp.go b/internal/bootstrap/configuration/gcp.go index 8acd18109..bd5724c7a 100644 --- a/internal/bootstrap/configuration/gcp.go +++ b/internal/bootstrap/configuration/gcp.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/bootstrap/provisioners/aws/provisioner.go b/internal/bootstrap/provisioners/aws/provisioner.go index 84634a4ee..8b2e5d6b9 100644 --- a/internal/bootstrap/provisioners/aws/provisioner.go +++ b/internal/bootstrap/provisioners/aws/provisioner.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/bootstrap/provisioners/gcp/provisioner.go b/internal/bootstrap/provisioners/gcp/provisioner.go index 1b3835439..05980e5a1 100644 --- a/internal/bootstrap/provisioners/gcp/provisioner.go +++ b/internal/bootstrap/provisioners/gcp/provisioner.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index 4e7972fc2..965998d1e 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/cluster/configuration/common.go b/internal/cluster/configuration/common.go index 96437fb41..bb411e278 100644 --- a/internal/cluster/configuration/common.go +++ b/internal/cluster/configuration/common.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/cluster/configuration/gke.go b/internal/cluster/configuration/gke.go index 9c5c6271e..bee9339fb 100644 --- a/internal/cluster/configuration/gke.go +++ b/internal/cluster/configuration/gke.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/cluster/configuration/vsphere.go b/internal/cluster/configuration/vsphere.go index c38e858d5..6ae86f79d 100644 --- a/internal/cluster/configuration/vsphere.go +++ b/internal/cluster/configuration/vsphere.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/cluster/provisioners/gke/provisioner.go b/internal/cluster/provisioners/gke/provisioner.go index 35f64d8c1..a60b3d87d 100644 --- a/internal/cluster/provisioners/gke/provisioner.go +++ b/internal/cluster/provisioners/gke/provisioner.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/cluster/provisioners/vsphere/provisioner.go b/internal/cluster/provisioners/vsphere/provisioner.go index 19ef73d26..ec998e99c 100644 --- a/internal/cluster/provisioners/vsphere/provisioner.go +++ b/internal/cluster/provisioners/vsphere/provisioner.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/configuration/assets/aws-bootstrap-file-state.yml b/internal/configuration/assets/aws-bootstrap-file-state.yml index ed055d902..a50711009 100644 --- a/internal/configuration/assets/aws-bootstrap-file-state.yml +++ b/internal/configuration/assets/aws-bootstrap-file-state.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/internal/configuration/assets/aws-bootstrap.yml b/internal/configuration/assets/aws-bootstrap.yml index aa0602d06..6101d688e 100644 --- a/internal/configuration/assets/aws-bootstrap.yml +++ b/internal/configuration/assets/aws-bootstrap.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/internal/configuration/assets/invalid-config.yml b/internal/configuration/assets/invalid-config.yml index e663a21ec..7373732eb 100644 --- a/internal/configuration/assets/invalid-config.yml +++ b/internal/configuration/assets/invalid-config.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/internal/configuration/assets/invalid-kind.yml b/internal/configuration/assets/invalid-kind.yml index 50a9d79e0..c9c43be7a 100644 --- a/internal/configuration/assets/invalid-kind.yml +++ b/internal/configuration/assets/invalid-kind.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/internal/configuration/assets/invalid-provisioner-bootstrap.yml b/internal/configuration/assets/invalid-provisioner-bootstrap.yml index 92a62b6ef..9535c9b7d 100644 --- a/internal/configuration/assets/invalid-provisioner-bootstrap.yml +++ b/internal/configuration/assets/invalid-provisioner-bootstrap.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020 SIGHUP s.r.l All rights reserved. +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. diff --git a/internal/configuration/config.go b/internal/configuration/config.go index 658b724e1..01970e5aa 100644 --- a/internal/configuration/config.go +++ b/internal/configuration/config.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/configuration/config_test.go b/internal/configuration/config_test.go index c5771c7b6..64a5fcf93 100644 --- a/internal/configuration/config_test.go +++ b/internal/configuration/config_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go index e6ec39d03..07582ce28 100644 --- a/internal/configuration/templates.go +++ b/internal/configuration/templates.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/io/fs.go b/internal/io/fs.go new file mode 100644 index 000000000..e526e2a3e --- /dev/null +++ b/internal/io/fs.go @@ -0,0 +1,95 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package io + +import ( + "bytes" + "fmt" + "github.com/sirupsen/logrus" + "io" + "os" + "path/filepath" + "strings" +) + +func CheckDirIsEmpty(target string) error { + if _, err := os.Stat(target); os.IsNotExist(err) { + return nil + } + + return filepath.Walk(target, func(path string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("the target directory is not empty, error while checking %s: %w", path, err) + } + + return fmt.Errorf("the target directory is not empty: %s", path) + }) +} + +func AppendBufferToFile(b bytes.Buffer, target string) error { + destination, err := os.OpenFile(target, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + + defer destination.Close() + + _, err = b.WriteTo(destination) + if err != nil { + return err + } + + return nil +} + +func CopyBufferToFile(b bytes.Buffer, source, target string) error { + if strings.TrimSpace(b.String()) == "" { + logrus.Printf("%s --> resulted in an empty file (%d bytes). Skipping.\n", source, b.Len()) + return nil + } + + logrus.Printf("%s --> %s\n", source, target) + + destination, err := os.Create(target) + if err != nil { + return err + } + + defer destination.Close() + + _, err = b.WriteTo(destination) + if err != nil { + return err + } + + return nil +} + +func CopyFromSourceToTarget(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + + defer destination.Close() + + return io.Copy(destination, source) +} diff --git a/internal/merge/merge.go b/internal/merge/merge.go new file mode 100644 index 000000000..926118839 --- /dev/null +++ b/internal/merge/merge.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package merge + +import ( + "fmt" +) + +type Merger struct { + base Mergeable + custom Mergeable +} + +func NewMerger(b, c Mergeable) *Merger { + return &Merger{ + base: b, + custom: c, + } +} + +func (m *Merger) Merge() (map[any]any, error) { + preparedBase, err := m.base.Get() + if err != nil { + return nil, fmt.Errorf("incorrect base file, %s", err.Error()) + } + + preparedCustom, err := m.custom.Get() + if err != nil { + return preparedBase, nil + } + + mergedSection := deepCopy(preparedBase, preparedCustom) + + err = m.base.Walk(mergedSection) + + return m.base.Content(), err +} + +func deepCopy(a, b map[any]any) map[any]any { + out := make(map[any]any, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[any]any); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[any]any); ok { + out[k] = deepCopy(bv, v) + continue + } + } + } + out[k] = v + } + return out +} diff --git a/internal/merge/merge_test.go b/internal/merge/merge_test.go new file mode 100644 index 000000000..59fedf689 --- /dev/null +++ b/internal/merge/merge_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package merge_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/sighupio/furyctl/internal/merge" +) + +func Test_NewMerger(t *testing.T) { + a := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "testName", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + b := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "testNewName", + "foo": "bar", + }, + "example": "string", + "test": map[any]any{ + "example": "string", + }, + }, + } + + merger := merge.NewMerger( + merge.NewDefaultModel(a, ".data.test"), + merge.NewDefaultModel(b, ".data.test"), + ) + + assert.NotEmpty(t, merger) +} + +func Test_Merge(t *testing.T) { + a := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "testName", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + b := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "testNewName", + "foo": "bar", + }, + "example": "string", + "test": map[any]any{ + "newTestString": "string", + }, + }, + } + + expectedRes := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "testName", + }, + "test": map[any]any{ + "newTestString": "string", + "testString": "lorem ipsum", + }, + }, + } + + merger := merge.NewMerger( + merge.NewDefaultModel(a, ".data.test"), + merge.NewDefaultModel(b, ".data.test"), + ) + + res, err := merger.Merge() + + assert.NoError(t, err) + assert.NotEmpty(t, res) + assert.Equal(t, expectedRes, res) +} diff --git a/internal/merge/model.go b/internal/merge/model.go new file mode 100644 index 000000000..282cdd540 --- /dev/null +++ b/internal/merge/model.go @@ -0,0 +1,78 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package merge + +import ( + "fmt" + "strings" +) + +type Mergeable interface { + Get() (map[any]any, error) + Walk(map[any]any) error + Content() map[any]any + Path() string +} + +type DefaultModel struct { + content map[any]any + path string +} + +func NewDefaultModel(content map[any]any, path string) *DefaultModel { + return &DefaultModel{ + content: content, + path: path, + } +} +func (b *DefaultModel) Content() map[any]any { + return b.content +} + +func (b *DefaultModel) Path() string { + return b.path +} + +func (b *DefaultModel) Get() (map[any]any, error) { + ret := b.content + + fields := strings.Split((*b).path[1:], ".") + + for _, f := range fields { + mapAtKey, ok := ret[f] + if !ok { + return nil, fmt.Errorf("cannot access key %s on map", f) + } + + ret, ok = mapAtKey.(map[any]any) + if !ok { + return nil, fmt.Errorf("data structure is invalid on key %s", f) + } + } + + return ret, nil +} + +func (b *DefaultModel) Walk(mergedSection map[any]any) error { + ret := b.content + + fields := strings.Split(b.Path()[1:], ".") + + for _, f := range fields[:len(fields)-1] { + _, ok := ret[f] + if !ok { + return fmt.Errorf("cannot access key %s on map", f) + } + + ret, ok = ret[f].(map[any]any) + if !ok { + return fmt.Errorf("data structure is invalid on key %s", f) + } + } + + ret[fields[len(fields)-1]] = mergedSection + + return nil +} diff --git a/internal/merge/model_test.go b/internal/merge/model_test.go new file mode 100644 index 000000000..a471a65ea --- /dev/null +++ b/internal/merge/model_test.go @@ -0,0 +1,137 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package merge_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/sighupio/furyctl/internal/merge" +) + +func TestNewDefaultModel(t *testing.T) { + content := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "example", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + path := ".data.test" + + model := merge.NewDefaultModel(content, path) + + assert.NotEmpty(t, model) + assert.Equal(t, content, model.Content()) + assert.Equal(t, path, model.Path()) +} + +func TestDefaultModel_Content(t *testing.T) { + content := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "example", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + path := ".data.test" + + model := merge.NewDefaultModel(content, path) + + assert.Equal(t, content, model.Content()) +} + +func TestDefaultModel_Path(t *testing.T) { + content := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "example", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + path := ".data.test" + + model := merge.NewDefaultModel(content, path) + + assert.Equal(t, path, model.Path()) +} + +func TestDefaultModel_Get(t *testing.T) { + content := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "example", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + expectedRes := map[any]any{ + "testString": "lorem ipsum", + } + + path := ".data.test" + + model := merge.NewDefaultModel(content, path) + + res, err := model.Get() + + assert.NoError(t, err) + assert.Equal(t, expectedRes, res) +} + +func TestDefaultModel_Walk(t *testing.T) { + content := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "example", + }, + "test": map[any]any{ + "testString": "lorem ipsum", + }, + }, + } + + target := map[any]any{ + "testString": "lorem ipsum", + "testNewString": "lorem ipsum new", + } + + expectedRes := map[any]any{ + "data": map[any]any{ + "meta": map[any]string{ + "name": "example", + }, + "test": map[any]any{ + "testNewString": "lorem ipsum new", + "testString": "lorem ipsum", + }, + }, + } + + path := ".data.test" + + model := merge.NewDefaultModel(content, path) + + err := model.Walk(target) + + assert.NoError(t, err) + assert.Equal(t, expectedRes, model.Content()) +} diff --git a/internal/project/project.go b/internal/project/project.go index af379c649..72f663713 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/provisioners/provisioners.go b/internal/provisioners/provisioners.go index 9367fe9fb..2772efd45 100644 --- a/internal/provisioners/provisioners.go +++ b/internal/provisioners/provisioners.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/internal/template/config.go b/internal/template/config.go new file mode 100644 index 000000000..229b4bc75 --- /dev/null +++ b/internal/template/config.go @@ -0,0 +1,18 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +type Templates struct { + Includes []string `yaml:"includes,omitempty"` + Excludes []string `yaml:"excludes,omitempty"` + Suffix string `yaml:"suffix,omitempty"` + ProcessFilename bool `yaml:"processFilename,omitempty"` +} + +type Config struct { + Data map[string]map[any]any `yaml:"data,omitempty"` + Include map[string]string `yaml:"include,omitempty"` + Templates Templates `yaml:"templates,omitempty"` +} diff --git a/internal/template/funcmap.go b/internal/template/funcmap.go new file mode 100644 index 000000000..361af7282 --- /dev/null +++ b/internal/template/funcmap.go @@ -0,0 +1,27 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "text/template" + + "github.com/Masterminds/sprig/v3" +) + +type FuncMap struct { + FuncMap template.FuncMap +} + +func NewFuncMap() FuncMap { + return FuncMap{FuncMap: sprig.TxtFuncMap()} +} + +func (f *FuncMap) Add(name string, fn interface{}) { + f.FuncMap[name] = fn +} + +func (f *FuncMap) Delete(name string) { + delete(f.FuncMap, name) +} diff --git a/internal/template/funcmap_test.go b/internal/template/funcmap_test.go new file mode 100644 index 000000000..89cad0f19 --- /dev/null +++ b/internal/template/funcmap_test.go @@ -0,0 +1,39 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template_test + +import ( + "github.com/sighupio/furyctl/internal/template" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewFuncMap(t *testing.T) { + f := template.NewFuncMap() + + assert.True(t, len(f.FuncMap) > 0) +} + +func TestFuncMap_Add(t *testing.T) { + f := template.NewFuncMap() + + f.Add("test", func() string { + return "test" + }) + + assert.NotNil(t, f.FuncMap["test"]) +} + +func TestFuncMap_Delete(t *testing.T) { + f := template.NewFuncMap() + + f.Add("test", func() string { + return "test" + }) + + f.Delete("test") + + assert.Nil(t, f.FuncMap["test"]) +} diff --git a/internal/template/generator.go b/internal/template/generator.go new file mode 100644 index 000000000..579b7a784 --- /dev/null +++ b/internal/template/generator.go @@ -0,0 +1,150 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "bytes" + "fmt" + "path/filepath" + "strings" + "text/template" + + "github.com/sighupio/furyctl/internal/io" + "github.com/sirupsen/logrus" +) + +type generator struct { + source string + target string + context map[string]map[any]any + funcMap FuncMap + dryRun bool +} + +func NewGenerator( + source, + target string, + context map[string]map[any]any, + funcMap FuncMap, + dryRun bool, +) *generator { + return &generator{ + source: source, + target: target, + context: context, + funcMap: funcMap, + dryRun: dryRun, + } +} + +func (g *generator) ProcessTemplate() *template.Template { + return template.Must( + template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source)) +} + +func (g *generator) GetMissingKeys(tpl *template.Template) []string { + var missingKeys []string + + node := NewNode() + node.FromNodeList(tpl.Tree.Root.Nodes) + + for _, f := range node.Fields { + val := g.getContextValueFromPath(f) + if val == nil { + missingKeys = append(missingKeys, f) + } + } + + return missingKeys +} + +func (g *generator) ProcessFile(tpl *template.Template) (bytes.Buffer, error) { + var generatedContent bytes.Buffer + + if !g.dryRun { + tpl.Option("missingkey=error") + } + + err := tpl.Execute(&generatedContent, g.context) + + return generatedContent, err +} + +func (g *generator) ProcessFilename( + tm *Model, +) (string, error) { + var realTarget string + + if tm.Config.Templates.ProcessFilename { //try to process filename as template + tpl := template.Must( + template.New("currentTarget").Funcs(g.funcMap.FuncMap).Parse(g.target)) + + destination := bytes.NewBufferString("") + + if err := tpl.Execute(destination, g.context); err != nil { + return "", err + } + realTarget = destination.String() + } else { + realTarget = g.target + } + + suf := tm.Suffix + if strings.HasSuffix(realTarget, suf) { + realTarget = realTarget[:len(realTarget)-len(tm.Suffix)] //cut off extension (.tmpl) from the end + } + + return realTarget, nil +} + +func (g *generator) UpdateTarget(newTarget string) { + g.target = newTarget +} + +func (g *generator) WriteMissingKeysToFile( + missingKeys []string, + tmplPath, + outputPath string, +) error { + if len(missingKeys) == 0 { + return nil + } + + logrus.Warnf( + "missing keys in template %s. Writing to %s/tmpl-debug.log\n", + tmplPath, + outputPath, + ) + + debugFilePath := filepath.Join(outputPath, "tmpl-debug.log") + + outLog := fmt.Sprintf("[%s]\n%s\n", tmplPath, strings.Join(missingKeys, "\n")) + + return io.AppendBufferToFile(*bytes.NewBufferString(outLog), debugFilePath) +} + +func (g *generator) getContextValueFromPath(path string) any { + paths := strings.Split(path[1:], ".") + + if len(paths) == 0 { + return nil + } + + ret := g.context[paths[0]] + + for _, key := range paths[1:] { + mapAtKey, ok := ret[key] + if !ok { + return nil + } + + ret, ok = mapAtKey.(map[any]any) + if !ok { + return mapAtKey + } + } + + return ret +} diff --git a/internal/template/generator_test.go b/internal/template/generator_test.go new file mode 100644 index 000000000..ee674eed5 --- /dev/null +++ b/internal/template/generator_test.go @@ -0,0 +1,171 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + + "github.com/sighupio/furyctl/internal/template" +) + +type Meta struct { + Name map[string]any `yaml:"name,flow"` +} + +func TestTemplateModel_Will_Generate_UserHello(t *testing.T) { + conf := map[string]any{ + "data": map[string]any{ + "meta": map[string]string{ + "name": "test", + }, + }, + } + + templateTest := "A nice day at {{.meta.name | substr 0 3}}" + + confYaml, err := yaml.Marshal(conf) + if err != nil { + panic(err) + } + + path, err := os.MkdirTemp("", "test") + + err = os.Mkdir(path+"/source", os.ModePerm) + err = os.Mkdir(path+"/target", os.ModePerm) + err = os.WriteFile(path+"/source/test.md.tpl", []byte(templateTest), os.ModePerm) + err = os.WriteFile(path+"/configTest.yaml", confYaml, os.ModePerm) + + defer os.RemoveAll(path) + + tm, err := template.NewTemplateModel( + path+"/source", + path+"/target", + path+"/configTest.yaml", + path, + ".tpl", + false, + false, + ) + + err = tm.Generate() + assert.NoError(t, err) + + result, err := os.ReadFile(path + "/target/test.md") + if err != nil { + panic(err) + } + + expectedRes := "A nice day at tes" + + assert.Equal(t, expectedRes, string(result)) +} + +func TestTemplateModel_Will_Generate_Dynamic_Values_From_Env(t *testing.T) { + conf := map[string]any{ + "data": map[string]any{ + "meta": Meta{ + Name: map[string]any{"env://TEST_USER_TYMLATE": ""}, + }, + }, + } + + templateTest := "A nice day at {{.meta.name | substr 0 3}}" + + confYaml, err := yaml.Marshal(conf) + if err != nil { + panic(err) + } + + path, err := os.MkdirTemp("", "test") + + err = os.Mkdir(path+"/source", os.ModePerm) + err = os.Mkdir(path+"/target", os.ModePerm) + err = os.WriteFile(path+"/source/test.md.tpl", []byte(templateTest), os.ModePerm) + err = os.WriteFile(path+"/configTest.yaml", confYaml, os.ModePerm) + + defer os.RemoveAll(path) + + tm, err := template.NewTemplateModel( + path+"/source", + path+"/target", + path+"/configTest.yaml", + path, + ".tpl", + false, + false, + ) + + os.Setenv("TEST_USER_TYMLATE", "Tymlate") + + defer os.Setenv("TEST_USER_TYMLATE", "") + + err = tm.Generate() + assert.NoError(t, err) + + result, err := os.ReadFile(path + "/target/test.md") + if err != nil { + panic(err) + } + + expectedRes := "A nice day at Tym" + + assert.Equal(t, expectedRes, string(result)) +} + +func TestTemplateModel_Will_Generate_Dynamic_Values_From_File(t *testing.T) { + path, err := os.MkdirTemp("", "test") + + conf := map[string]any{ + "data": map[string]any{ + "meta": Meta{ + Name: map[string]any{"file://" + path + "/tymlate_test_file.txt": ""}, + }, + }, + } + + templateTest := "A nice day at {{.meta.name}}" + + confYaml, err := yaml.Marshal(conf) + if err != nil { + panic(err) + } + + err = os.Mkdir(path+"/source", os.ModePerm) + err = os.Mkdir(path+"/target", os.ModePerm) + err = os.WriteFile(path+"/source/test.md.tpl", []byte(templateTest), os.ModePerm) + err = os.WriteFile(path+"/configTest.yaml", confYaml, os.ModePerm) + + defer os.RemoveAll(path) + + tm, err := template.NewTemplateModel( + path+"/source", + path+"/target", + path+"/configTest.yaml", + path, + ".tpl", + false, + false, + ) + + exampleStr := "Tymlate! It's a nice day!" + + os.WriteFile(path+"/tymlate_test_file.txt", []byte(exampleStr), os.ModePerm) + + err = tm.Generate() + assert.NoError(t, err) + + result, err := os.ReadFile(path + "/target/test.md") + if err != nil { + panic(err) + } + + expectedRes := "A nice day at Tymlate! It's a nice day!" + + assert.Equal(t, expectedRes, string(result)) +} diff --git a/internal/template/mapper/mapper.go b/internal/template/mapper/mapper.go new file mode 100644 index 000000000..55f64d364 --- /dev/null +++ b/internal/template/mapper/mapper.go @@ -0,0 +1,109 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mapper + +import ( + "os" + "strings" +) + +const ( + Env = "env" + File = "file" +) + +type Mapper struct { + context map[string]map[any]any +} + +func NewMapper(context map[string]map[any]any) *Mapper { + return &Mapper{context: context} +} + +func (m *Mapper) MapDynamicValues() (map[string]map[any]any, error) { + mappedCtx := make(map[string]map[any]any, len(m.context)) + + for k, c := range m.context { + res, err := injectDynamicRes(c, c, k) + mappedCtx[k] = res + + if err != nil { + return nil, err + } + } + + return mappedCtx, nil +} + +func (m *Mapper) MapEnvironmentVars() map[any]any { + envMap := make(map[any]any) + + for _, v := range os.Environ() { + part := strings.Split(v, "=") + envMap[part[0]] = part[1] + } + + return envMap +} + +func injectDynamicRes( + m map[any]any, + parent map[any]any, + parentKey string, +) (map[any]any, error) { + for k, v := range m { + spl := strings.Split(k.(string), "://") + + if len(spl) > 1 { + + source := spl[0] + sourceValue := spl[1] + + switch source { + case Env: + envVar := os.Getenv(sourceValue) + parent[parentKey] = envVar + case File: + content, err := readValueFromFile(sourceValue) + if err != nil { + return nil, err + } + parent[parentKey] = content + } + + continue + } + + vMap, checkMap := v.(map[any]any) + if checkMap { + if _, err := injectDynamicRes(vMap, m, k.(string)); err != nil { + return nil, err + } + + continue + } + + vArr, checkArr := v.([]any) + if checkArr { + for _, j := range vArr { + if j, ok := j.(map[any]any); ok { + if _, err := injectDynamicRes(j, m, k.(string)); err != nil { + return nil, err + } + } + } + + continue + } + } + + return m, nil +} + +func readValueFromFile(path string) (string, error) { + val, err := os.ReadFile(path) + + return string(val), err +} diff --git a/internal/template/mapper/mapper_test.go b/internal/template/mapper/mapper_test.go new file mode 100644 index 000000000..3c16d0533 --- /dev/null +++ b/internal/template/mapper/mapper_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mapper_test + +import ( + "fmt" + "github.com/sighupio/furyctl/internal/template/mapper" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestNewMapper(t *testing.T) { + dummyContext := map[string]map[any]any{ + "data": { + "meta": map[any]any{ + "name": "test", + }, + }, + } + + m := mapper.NewMapper(dummyContext) + + assert.NotNil(t, m) +} + +func TestMapper_MapEnvironmentVars(t *testing.T) { + dummyContext := map[string]map[any]any{ + "data": { + "meta": map[any]any{ + "name": "test", + }, + }, + } + + expectedEnvMap := map[string]string{ + "TEST_MAPPER_ENV": "test", + } + + m := mapper.NewMapper(dummyContext) + + err := os.Setenv("TEST_MAPPER_ENV", "test") + + assert.NoError(t, err) + + defer os.Setenv("TEST_MAPPER_ENV", "") + + envMap := m.MapEnvironmentVars() + + assert.Equal(t, expectedEnvMap["TEST_MAPPER_ENV"], envMap["TEST_MAPPER_ENV"]) +} + +func TestMapper_MapDynamicValues(t *testing.T) { + path, err := os.MkdirTemp("", "test") + + assert.NoError(t, err) + + exampleStr := "test!" + + err = os.WriteFile(path+"/test_file.txt", []byte(exampleStr), os.ModePerm) + + defer os.RemoveAll(path) + + assert.NoError(t, err) + + dummyContext := map[string]map[any]any{ + "data": { + "meta": map[any]any{ + "name": map[any]any{"env://TEST_MAPPER_DYNAMIC_VALUE": ""}, + "value": map[any]any{fmt.Sprintf("file://%s/test_file.txt", path): ""}, + }, + }, + } + + m := mapper.NewMapper(dummyContext) + + err = os.Setenv("TEST_MAPPER_DYNAMIC_VALUE", "test") + + assert.NoError(t, err) + + defer os.Setenv("TEST_MAPPER_DYNAMIC_VALUE", "") + + filledContext, err := m.MapDynamicValues() + + assert.NoError(t, err) + + meta := filledContext["data"]["meta"] + + mapMeta, ok := meta.(map[any]any) + + assert.True(t, ok) + + assert.Equal(t, "test", mapMeta["name"]) + + assert.Equal(t, exampleStr, mapMeta["value"]) +} diff --git a/internal/template/model.go b/internal/template/model.go new file mode 100644 index 000000000..0cd12e5a0 --- /dev/null +++ b/internal/template/model.go @@ -0,0 +1,218 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/sighupio/furyctl/internal/io" + "github.com/sighupio/furyctl/internal/template/mapper" + yaml2 "github.com/sighupio/furyctl/internal/yaml" + fTemplate "github.com/sighupio/furyctl/pkg/template" + + "gopkg.in/yaml.v2" +) + +type Model struct { + SourcePath string + TargetPath string + ConfigPath string + OutputPath string + Config Config + Suffix string + Context map[string]map[any]any + FuncMap FuncMap + StopIfTargetNotEmpty bool + DryRun bool +} + +func NewTemplateModel( + source, + target, + configPath, + outPath, + suffix string, + stopIfNotEmpty, + dryRun bool, +) (*Model, error) { + var model Config + + if len(source) < 1 { + return nil, fmt.Errorf("source must be set") + } + + if len(target) < 1 { + return nil, fmt.Errorf("target must be set") + } + + if len(configPath) > 0 { + readFile, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, err + } + + if err = yaml.Unmarshal(readFile, &model); err != nil { + return nil, err + } + } + + if stopIfNotEmpty { + err := io.CheckDirIsEmpty(target) + if err != nil { + return nil, err + } + } + + funcMap := NewFuncMap() + funcMap.Add("toYaml", fTemplate.ToYAML) + funcMap.Add("fromYaml", fTemplate.FromYAML) + + return &Model{ + SourcePath: source, + TargetPath: target, + ConfigPath: configPath, + OutputPath: outPath, + Config: model, + Suffix: suffix, + FuncMap: funcMap, + StopIfTargetNotEmpty: stopIfNotEmpty, + DryRun: dryRun, + }, nil +} + +func (tm *Model) isExcluded(source string) bool { + for _, exc := range tm.Config.Templates.Excludes { + regex := regexp.MustCompile(exc) + if regex.MatchString(source) { + return true + } + } + return false +} + +func (tm *Model) Generate() error { + osErr := os.MkdirAll(tm.TargetPath, os.ModePerm) + if osErr != nil { + return osErr + } + + context, cErr := tm.generateContext() + if cErr != nil { + return cErr + } + + ctxMapper := mapper.NewMapper(context) + + context, err := ctxMapper.MapDynamicValues() + if err != nil { + return err + } + + tm.Context = context + + return filepath.Walk(tm.SourcePath, tm.applyTemplates) +} + +func (tm *Model) applyTemplates( + relSource string, + info os.FileInfo, + err error, +) error { + if tm.isExcluded(relSource) { + return err + } + + if info.IsDir() { + return err + } + + rel, err := filepath.Rel(tm.SourcePath, relSource) + if err != nil { + return err + } + + currentTarget := filepath.Join(tm.TargetPath, rel) + + gen := NewGenerator( + relSource, + currentTarget, + tm.Context, + tm.FuncMap, + tm.DryRun, + ) + + realTarget, fErr := gen.ProcessFilename(tm) + if fErr != nil { //maybe we should fail back to real name instead? + return fErr + } + + gen.UpdateTarget(realTarget) + + currentTargetDir := filepath.Dir(realTarget) + + if _, err := os.Stat(currentTargetDir); os.IsNotExist(err) { + if err := os.MkdirAll(currentTargetDir, os.ModePerm); err != nil { + return err + } + } + + if strings.HasSuffix(info.Name(), tm.Suffix) { + tmpl := gen.ProcessTemplate() + + if tmpl == nil { + return fmt.Errorf("no template found for %s", relSource) + } + + if tm.DryRun { + missingKeys := gen.GetMissingKeys(tmpl) + + err := gen.WriteMissingKeysToFile(missingKeys, relSource, tm.OutputPath) + if err != nil { + return err + } + } + + content, cErr := gen.ProcessFile(tmpl) + if cErr != nil { + return fmt.Errorf("%+v filePath: %s", cErr, relSource) + } + + return io.CopyBufferToFile(content, relSource, realTarget) + } + + _, err = io.CopyFromSourceToTarget(relSource, realTarget) + + return err +} + +func (tm *Model) generateContext() (map[string]map[any]any, error) { + context := make(map[string]map[any]any) + + for k, v := range tm.Config.Data { + context[k] = v + } + + for k, v := range tm.Config.Include { + cPath := filepath.Join(filepath.Dir(tm.ConfigPath), v) + + if filepath.IsAbs(v) { + cPath = v + } + + yamlConfig, err := yaml2.FromFile[map[any]any](cPath) + if err != nil { + return nil, err + } + + context[k] = yamlConfig + } + + return context, nil +} diff --git a/internal/template/model_test.go b/internal/template/model_test.go new file mode 100644 index 000000000..3f148d628 --- /dev/null +++ b/internal/template/model_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template_test + +import ( + "github.com/sighupio/furyctl/internal/template" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + "os" + "testing" +) + +func TestNewTemplateModel(t *testing.T) { + conf := map[string]interface{}{ + "data": map[string]interface{}{ + "meta": map[string]string{ + "name": "test", + }, + }, + } + + templateTest := "A nice day at {{.meta.name | substr 0 3}}" + + confYaml, err := yaml.Marshal(conf) + if err != nil { + panic(err) + } + + path, err := os.MkdirTemp("", "test") + + err = os.Mkdir(path+"/source", os.ModePerm) + err = os.Mkdir(path+"/target", os.ModePerm) + err = os.WriteFile(path+"/source/test.md.tpl", []byte(templateTest), os.ModePerm) + err = os.WriteFile(path+"/configTest.yaml", confYaml, os.ModePerm) + + defer os.RemoveAll(path) + + tm, err := template.NewTemplateModel( + path+"/source", + path+"/target", + path+"/configTest.yaml", + path, + ".tpl", + false, + false, + ) + + assert.NoError(t, err) + assert.NotNil(t, tm) +} diff --git a/internal/template/node.go b/internal/template/node.go new file mode 100644 index 000000000..a7995b900 --- /dev/null +++ b/internal/template/node.go @@ -0,0 +1,151 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "reflect" + "strings" + "text/template/parse" +) + +var ( + // MapParseNodeToAlias is a map of parse.Node to its alias. + MapParseNodeToAlias = map[parse.NodeType]interface{}{ + parse.NodeList: &ListNode{}, + parse.NodeRange: &RangeNode{}, + parse.NodePipe: &PipeNode{}, + parse.NodeTemplate: &TemplateNode{}, + parse.NodeIf: &IfNode{}, + parse.NodeAction: &ActionNode{}, + parse.NodeField: &FieldNode{}, + parse.NodeVariable: &VariableNode{}, + } +) + +type Node struct { + Fields []string +} + +func NewNode() *Node { + return &Node{ + Fields: []string{}, + } +} + +func (f *Node) Set(s []string) { + f.Fields = s +} + +func (f *Node) FromNodeList(nodes []parse.Node) []string { + for _, n := range nodes { + setter, ok := mapToAliasInterface(n).(FieldsSetter) + if ok { + setter.Set(f) + } + } + + return f.Fields +} + +func mapToAliasInterface(n parse.Node) interface{} { + t := MapParseNodeToAlias[n.Type()] + + if t == nil { + return nil + } + + return reflect.ValueOf(n).Convert(reflect.TypeOf(t)).Interface() +} + +type FieldsSetter interface { + Set(n *Node) +} + +type ListNode parse.ListNode + +func (l *ListNode) Set(n *Node) { + n.Set(n.FromNodeList(l.Nodes)) +} + +type RangeNode parse.RangeNode + +func (r *RangeNode) Set(n *Node) { + if r.Pipe != nil { + for _, cmd := range r.Pipe.Cmds { + n.Set(n.FromNodeList(cmd.Args)) + } + } + + if r.List != nil { + n.Set(n.FromNodeList(r.List.Nodes)) + } + + if r.ElseList != nil { + n.Set(n.FromNodeList(r.ElseList.Nodes)) + } +} + +type PipeNode parse.PipeNode + +func (p *PipeNode) Set(n *Node) { + for _, cmd := range p.Cmds { + n.Set(n.FromNodeList(cmd.Args)) + } +} + +type TemplateNode parse.TemplateNode + +func (t *TemplateNode) Set(n *Node) { + if t.Pipe != nil { + for _, cmd := range t.Pipe.Cmds { + n.Set(n.FromNodeList(cmd.Args)) + } + } +} + +type IfNode parse.IfNode + +func (i *IfNode) Set(n *Node) { + for _, cmd := range i.BranchNode.Pipe.Cmds { + n.Set(n.FromNodeList(cmd.Args)) + } + + if i.BranchNode.List != nil { + n.Set(n.FromNodeList(i.BranchNode.List.Nodes)) + } + + if i.BranchNode.ElseList != nil { + n.Set(n.FromNodeList(i.BranchNode.ElseList.Nodes)) + } +} + +type ActionNode parse.ActionNode + +func (a *ActionNode) Set(n *Node) { + for _, cmd := range a.Pipe.Cmds { + n.Set(n.FromNodeList(cmd.Args)) + } +} + +type FieldNode parse.FieldNode + +func (f *FieldNode) Set(n *Node) { + n.Set(append(n.Fields, stringsToPath(f.Ident))) +} + +type VariableNode parse.VariableNode + +func (v *VariableNode) Set(n *Node) { + n.Set(append(n.Fields, stringsToPath(v.Ident))) +} + +func stringsToPath(s []string) string { + var sb strings.Builder + for _, s := range s { + sb.WriteByte('.') + sb.WriteString(s) + } + return sb.String() +} diff --git a/internal/template/node_test.go b/internal/template/node_test.go new file mode 100644 index 000000000..46e272c49 --- /dev/null +++ b/internal/template/node_test.go @@ -0,0 +1,359 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template_test + +import ( + template2 "github.com/sighupio/furyctl/internal/template" + "github.com/stretchr/testify/assert" + "reflect" + "testing" + "text/template" + "text/template/parse" +) + +func TestNewNode(t *testing.T) { + node := template2.NewNode() + + assert.Equal(t, []string{}, node.Fields) +} + +func TestNode_FromNodeList(t *testing.T) { + node := template2.NewNode() + tmpl := template.Must(template.New("test").Parse("{{.field1}}")) + node.FromNodeList(tmpl.Root.Nodes) + + assert.Equal(t, []string{".field1"}, node.Fields) +} + +func TestActionNode_Set(t *testing.T) { + node := template2.NewNode() + actionNode := &parse.ActionNode{ + NodeType: parse.NodeAction, + Pos: 1, + Line: 1, + Pipe: &parse.PipeNode{ + NodeType: parse.NodePipe, + Pos: 1, + Line: 1, + IsAssign: false, + Decl: nil, + Cmds: []*parse.CommandNode{ + { + NodeType: parse.NodeCommand, + Pos: 1, + Args: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + }, + }, + }, + }, + }, + } + + tmplActionNode := reflect.ValueOf(actionNode).Convert(reflect.TypeOf(&template2.ActionNode{})).Interface() + + assert.NotNil(t, tmplActionNode) + + actionNodeSetter, ok := tmplActionNode.(template2.FieldsSetter) + + assert.True(t, ok) + + actionNodeSetter.Set(node) + + assert.Equal(t, []string{".field1"}, node.Fields) +} + +func TestFieldNode_Set(t *testing.T) { + node := template2.NewNode() + fieldNode := &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + } + + tmplFieldNode := reflect.ValueOf(fieldNode).Convert(reflect.TypeOf(&template2.FieldNode{})).Interface() + + assert.NotNil(t, tmplFieldNode) + + fieldNodeSetter, ok := tmplFieldNode.(template2.FieldsSetter) + + assert.True(t, ok) + + fieldNodeSetter.Set(node) + + assert.Equal(t, []string{".field1"}, node.Fields) +} + +func TestIfNode_Set(t *testing.T) { + node := template2.NewNode() + ifNode := &parse.IfNode{ + BranchNode: parse.BranchNode{ + NodeType: parse.NodeIf, + Pos: 1, + Line: 1, + Pipe: &parse.PipeNode{ + NodeType: parse.NodePipe, + Pos: 1, + Line: 1, + IsAssign: false, + Decl: nil, + Cmds: []*parse.CommandNode{ + { + NodeType: parse.NodeCommand, + Pos: 1, + Args: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + }, + }, + }, + }, + }, + List: &parse.ListNode{ + NodeType: parse.NodeList, + Pos: 1, + Nodes: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field2"}, + }, + }, + }, + ElseList: &parse.ListNode{ + NodeType: parse.NodeList, + Pos: 1, + Nodes: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field3"}, + }, + }, + }, + }, + } + + tmplIfNode := reflect.ValueOf(ifNode).Convert(reflect.TypeOf(&template2.IfNode{})).Interface() + + assert.NotNil(t, tmplIfNode) + + ifNodeSetter, ok := tmplIfNode.(template2.FieldsSetter) + + assert.True(t, ok) + + ifNodeSetter.Set(node) + + assert.Equal(t, []string{".field1", ".field2", ".field3"}, node.Fields) +} + +func TestListNode_Set(t *testing.T) { + node := template2.NewNode() + listNode := &parse.ListNode{ + NodeType: parse.NodeList, + Pos: 1, + Nodes: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + }, + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field2"}, + }, + }, + } + + tmplListNode := reflect.ValueOf(listNode).Convert(reflect.TypeOf(&template2.ListNode{})).Interface() + + assert.NotNil(t, tmplListNode) + + listNodeSetter, ok := tmplListNode.(template2.FieldsSetter) + + assert.True(t, ok) + + listNodeSetter.Set(node) + + assert.Equal(t, []string{".field1", ".field2"}, node.Fields) +} + +func TestNode_Set(t *testing.T) { + node := template2.NewNode() + + node.Set([]string{".field1", ".field2"}) + + assert.Equal(t, []string{".field1", ".field2"}, node.Fields) +} + +func TestVariableNode_Set(t *testing.T) { + node := template2.NewNode() + variableNode := &parse.VariableNode{ + NodeType: parse.NodeVariable, + Pos: 1, + Ident: []string{"variable1"}, + } + + tmplVariableNode := reflect.ValueOf(variableNode).Convert(reflect.TypeOf(&template2.VariableNode{})).Interface() + + assert.NotNil(t, tmplVariableNode) + + variableNodeSetter, ok := tmplVariableNode.(template2.FieldsSetter) + + assert.True(t, ok) + + variableNodeSetter.Set(node) + + assert.Equal(t, []string{".variable1"}, node.Fields) +} + +func TestRangeNode_Set(t *testing.T) { + node := template2.NewNode() + rangeNode := &parse.RangeNode{ + BranchNode: parse.BranchNode{ + NodeType: parse.NodeIf, + Pos: 1, + Line: 1, + Pipe: &parse.PipeNode{ + NodeType: parse.NodePipe, + Pos: 1, + Line: 1, + IsAssign: false, + Decl: nil, + Cmds: []*parse.CommandNode{ + { + NodeType: parse.NodeCommand, + Pos: 1, + Args: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + }, + }, + }, + }, + }, + List: &parse.ListNode{ + NodeType: parse.NodeList, + Pos: 1, + Nodes: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field2"}, + }, + }, + }, + ElseList: &parse.ListNode{ + NodeType: parse.NodeList, + Pos: 1, + Nodes: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field3"}, + }, + }, + }, + }, + } + + tmplRangeNode := reflect.ValueOf(rangeNode).Convert(reflect.TypeOf(&template2.RangeNode{})).Interface() + + assert.NotNil(t, tmplRangeNode) + + rangeNodeSetter, ok := tmplRangeNode.(template2.FieldsSetter) + + assert.True(t, ok) + + rangeNodeSetter.Set(node) + + assert.Equal(t, []string{".field1", ".field2", ".field3"}, node.Fields) +} + +func TestPipeNode_Set(t *testing.T) { + node := template2.NewNode() + pipeNode := &parse.PipeNode{ + NodeType: parse.NodePipe, + Pos: 1, + Line: 1, + IsAssign: false, + Decl: nil, + Cmds: []*parse.CommandNode{ + { + NodeType: parse.NodeCommand, + Pos: 1, + Args: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + }, + }, + }, + }, + } + + tmplPipeNode := reflect.ValueOf(pipeNode).Convert(reflect.TypeOf(&template2.PipeNode{})).Interface() + + assert.NotNil(t, tmplPipeNode) + + pipeNodeSetter, ok := tmplPipeNode.(template2.FieldsSetter) + + assert.True(t, ok) + + pipeNodeSetter.Set(node) + + assert.Equal(t, []string{".field1"}, node.Fields) +} + +func TestTemplateNode_Set(t *testing.T) { + node := template2.NewNode() + templateNode := &parse.TemplateNode{ + NodeType: parse.NodeTemplate, + Pos: 1, + Line: 1, + Name: "template1", + Pipe: &parse.PipeNode{ + NodeType: parse.NodePipe, + Pos: 1, + Line: 1, + IsAssign: false, + Decl: nil, + Cmds: []*parse.CommandNode{ + { + NodeType: parse.NodeCommand, + Pos: 1, + Args: []parse.Node{ + &parse.FieldNode{ + NodeType: parse.NodeField, + Pos: 1, + Ident: []string{"field1"}, + }, + }, + }, + }, + }, + } + + tmplTemplateNode := reflect.ValueOf(templateNode).Convert(reflect.TypeOf(&template2.TemplateNode{})).Interface() + + assert.NotNil(t, tmplTemplateNode) + + templateNodeSetter, ok := tmplTemplateNode.(template2.FieldsSetter) + + assert.True(t, ok) + + templateNodeSetter.Set(node) + + assert.Equal(t, []string{".field1"}, node.Fields) +} diff --git a/internal/yaml/yaml.go b/internal/yaml/yaml.go new file mode 100644 index 000000000..a294ae696 --- /dev/null +++ b/internal/yaml/yaml.go @@ -0,0 +1,24 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package yaml + +import ( + "gopkg.in/yaml.v2" + "os" +) + +func FromFile[T any](path string) (T, error) { + var yamlRes T + + res, err := os.ReadFile(path) + if err != nil { + return yamlRes, err + } + + err = yaml.Unmarshal(res, &yamlRes) + + return yamlRes, err + +} diff --git a/internal/yaml/yaml_test.go b/internal/yaml/yaml_test.go new file mode 100644 index 000000000..4bb8097ff --- /dev/null +++ b/internal/yaml/yaml_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package yaml_test + +import ( + yaml2 "github.com/sighupio/furyctl/internal/yaml" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + "os" + "testing" +) + +type TestYaml struct { + Test string `yaml:"test"` +} + +func TestFromFile(t *testing.T) { + test := TestYaml{ + "test", + } + + path, err := os.MkdirTemp("", "test") + + assert.NoError(t, err) + + file, err := os.Create(path + "/test.yaml") + + assert.NoError(t, err) + + testBytes, err := yaml.Marshal(test) + + assert.NoError(t, err) + + _, err = file.Write(testBytes) + + assert.NoError(t, err) + + defer os.RemoveAll(path) + + testRes, err := yaml2.FromFile[TestYaml](path + "/test.yaml") + + assert.NoError(t, err) + + assert.Equal(t, test, testRes) +} diff --git a/main.go b/main.go index 4fa356292..b2b91980d 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Copyright © 2018 Sighup SRL support@sighup.io +// Copyright © 2017-present SIGHUP SRL support@sighup.io // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 0356965fc..c478e604b 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/pkg/template/funcmap.go b/pkg/template/funcmap.go new file mode 100644 index 000000000..e760d3880 --- /dev/null +++ b/pkg/template/funcmap.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "strings" + + "gopkg.in/yaml.v2" +) + +func ToYAML(v any) string { + data, err := yaml.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return strings.TrimSuffix(string(data), "\n") +} + +func FromYAML(str string) map[string]any { + m := map[string]any{} + + if err := yaml.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} diff --git a/pkg/terraform/install.go b/pkg/terraform/install.go index ef91ff934..dd66d7167 100644 --- a/pkg/terraform/install.go +++ b/pkg/terraform/install.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index 7ab0e04f2..b9691523c 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 142b260dc..704b720c3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 SIGHUP s.r.l All rights reserved. +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. From 5676ca43292b34ca748e075859d4d1c4201fdca3 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Tue, 13 Sep 2022 10:15:08 +0200 Subject: [PATCH 002/383] chore: wip --- cmd/validate.go | 45 +++++++++++ cmd/validate/util.go | 123 +++++++++++++++++++++++++++++ go.mod | 4 +- go.sum | 7 +- internal/schema/santhosh/errors.go | 110 ++++++++++++++++++++++++++ internal/schema/santhosh/loader.go | 28 +++++++ 6 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 cmd/validate.go create mode 100644 cmd/validate/util.go create mode 100644 internal/schema/santhosh/errors.go create mode 100644 internal/schema/santhosh/loader.go diff --git a/cmd/validate.go b/cmd/validate.go new file mode 100644 index 000000000..6e12e9786 --- /dev/null +++ b/cmd/validate.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/cmd/validate" + "github.com/sighupio/furyctl/internal/schema/santhosh" +) + +var configPath string +var output string + +var validateCmd = &cobra.Command{ + Use: "validate", + Short: "Validate Furyfile", + RunE: func(cmd *cobra.Command, args []string) error { + schemaLocation := "https://raw.githubusercontent.com/sighupio/fury-distribution/feature/create-draft-of-the-furyctl-yaml-json-schema" + + schema, err := santhosh.LoadSchema(schemaLocation) + if err != nil { + return fmt.Errorf("failed to load schema: %w", err) + } + + hasErrors := error(nil) + + conf := validate.LoadConfig[validate.FuryDistributionSpecs](configPath) + if err := schema.ValidateInterface(conf); err != nil { + validate.PrintResults(output, err, conf, configPath) + + hasErrors = validate.ErrHasValidationErrors + } + + validate.PrintSummary(output, hasErrors != nil) + + return hasErrors + }, +} + +func init() { + validateCmd.Flags().StringP("config", "c", configPath, "Furyctl config file path") + validateCmd.Flags().StringP("output", "o", "text", "Output format (text|json)") + rootCmd.AddCommand(validateCmd) +} diff --git a/cmd/validate/util.go b/cmd/validate/util.go new file mode 100644 index 000000000..ca03cb2d6 --- /dev/null +++ b/cmd/validate/util.go @@ -0,0 +1,123 @@ +package validate + +import ( + "encoding/json" + "errors" + "fmt" + "os" + + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" + + "github.com/sighupio/furyctl/internal/schema/santhosh" +) + +var ( + ErrHasValidationErrors = errors.New("schema has validation errors") + ErrUnknownOutputFormat = errors.New("unknown output format") +) + +type FuryDistributionSpecs struct { + Version string `json:"version"` + Kind string `json:"kind"` +} + +func LoadConfig[T any](file string) T { + var conf T + + configData, err := os.ReadFile(file) + if err != nil { + log.Fatal(err) + } + + if err := yaml.Unmarshal(configData, &conf); err != nil { + log.Fatal(err) + } + + return conf +} + +func PrintSummary(output string, hasErrors bool) { + switch output { + case "text": + if hasErrors { + fmt.Println("Validation failed") + } else { + fmt.Println("Validation succeeded") + } + + case "json": + if hasErrors { + fmt.Println("{\"result\":\"Validation failed\"}") + } else { + fmt.Println("{\"result\":\"Validation succeeded\"}") + } + + default: + log.Fatal(fmt.Errorf("'%s': %w", output, ErrUnknownOutputFormat)) + } +} + +func PrintResults(output string, err error, conf any, configFile string) { + ptrPaths := santhosh.GetPtrPaths(err) + + switch output { + case "text": + printTextResults(ptrPaths, err, conf, configFile) + + case "json": + printJSONResults(ptrPaths, err, conf, configFile) + + default: + log.Fatal(fmt.Errorf("'%s': %w", output, ErrUnknownOutputFormat)) + } +} + +func printTextResults(ptrPaths [][]any, err error, conf any, configFile string) { + fmt.Printf("CONFIG FILE %s\n", configFile) + + for _, path := range ptrPaths { + value, serr := santhosh.GetValueAtPath(conf, path) + if serr != nil { + log.Fatal(serr) + } + + fmt.Printf( + "path '%s' contains an invalid configuration value: %+v\n", + santhosh.JoinPtrPath(path), + value, + ) + } + + fmt.Println(err) +} + +func printJSONResults(ptrPaths [][]any, err error, conf any, configFile string) { + for _, path := range ptrPaths { + value, serr := santhosh.GetValueAtPath(conf, path) + if serr != nil { + log.Fatal(serr) + } + + jv, jerr := json.Marshal( + map[string]any{ + "file": configFile, + "message": "path contains an invalid configuration value", + "path": santhosh.JoinPtrPath(path), + "value": value, + }, + ) + if jerr != nil { + log.Fatal(jerr) + } + + fmt.Println(jv) + } + + jv, jerr := json.Marshal(err) + if jerr != nil { + log.Fatal(jerr) + } + + fmt.Println(jv) +} diff --git a/go.mod b/go.mod index 20a335e49..c9c63363b 100644 --- a/go.mod +++ b/go.mod @@ -17,10 +17,12 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 github.com/relex/aini v1.2.1 github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 + golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 golang.org/x/term v0.5.0 // indirect golang.org/x/tools v0.5.0 // indirect gopkg.in/yaml.v2 v2.4.0 @@ -68,7 +70,6 @@ require ( github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/pelletier/go-toml v1.9.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.3.1 // indirect @@ -84,7 +85,6 @@ require ( golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect - golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect google.golang.org/api v0.44.0 // indirect diff --git a/go.sum b/go.sum index d099cefbe..749eb1e39 100644 --- a/go.sum +++ b/go.sum @@ -176,8 +176,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -350,6 +350,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -449,6 +451,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -672,7 +676,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/internal/schema/santhosh/errors.go b/internal/schema/santhosh/errors.go new file mode 100644 index 000000000..0d65957c5 --- /dev/null +++ b/internal/schema/santhosh/errors.go @@ -0,0 +1,110 @@ +package santhosh + +import ( + "errors" + "strconv" + "strings" + + "github.com/santhosh-tekuri/jsonschema" + "golang.org/x/exp/slices" +) + +var ErrObjTypeAssertion = errors.New("obj type assertion failed") + +func JoinPtrPath(path []any) string { + strpath := "#" + + for _, key := range path { + switch v := key.(type) { + case int: + strpath += "/" + strconv.Itoa(v) + + case string: + strpath += "/" + v + } + } + + return strpath +} + +func GetValueAtPath(obj any, path []any) (any, error) { + for _, key := range path { + switch v := key.(type) { + case int: + tobj, ok := obj.([]any) + if !ok { + return nil, ErrObjTypeAssertion + } + + obj = tobj[v] + + case string: + tobj, ok := obj.(map[string]any) + if !ok { + return nil, ErrObjTypeAssertion + } + + obj = tobj[v] + } + } + + return obj, nil +} + +func GetPtrPaths(err error) [][]any { + var terr *jsonschema.ValidationError + + if errors.As(err, &terr) { + ptrs := extractPtrs(terr) + + mptrs := minimizePtrs(ptrs) + + return explodePtrs(mptrs) + } + + return nil +} + +func extractPtrs(err *jsonschema.ValidationError) []string { + ptrs := []string{err.InstancePtr} + + for _, cause := range err.Causes { + if len(cause.Causes) > 0 { + ptrs = append(ptrs, extractPtrs(cause)...) + } else { + ptrs = append(ptrs, cause.InstancePtr) + } + } + + return ptrs +} + +func minimizePtrs(ptrs []string) []string { + slices.Sort(ptrs) + + return slices.Compact(ptrs) +} + +func explodePtrs(ptrs []string) [][]any { + eptrs := make([][]any, len(ptrs)) + for i, p := range ptrs { + eptrs[i] = explodePtr(strings.TrimLeft(p, "#/")) + } + + return eptrs +} + +func explodePtr(ptr string) []any { + parts := strings.Split(ptr, "/") + ptrParts := make([]any, len(parts)) + + for i, part := range parts { + if numpart, err := strconv.Atoi(part); err == nil { + ptrParts[i] = numpart + } else { + ptrParts[i] = part + } + } + + return ptrParts +} diff --git a/internal/schema/santhosh/loader.go b/internal/schema/santhosh/loader.go new file mode 100644 index 000000000..f7ce4f54e --- /dev/null +++ b/internal/schema/santhosh/loader.go @@ -0,0 +1,28 @@ +package santhosh + +import ( + "bytes" + "fmt" + "os" + + "github.com/santhosh-tekuri/jsonschema" +) + +func LoadSchema(schemaPath string) (schema *jsonschema.Schema, errSchema error) { + data, err := os.ReadFile(schemaPath) + if err != nil { + return nil, fmt.Errorf("failed to read schema file: %w", err) + } + + compiler := jsonschema.NewCompiler() + if err = compiler.AddResource(schemaPath, bytes.NewReader(data)); err != nil { + return nil, fmt.Errorf("failed to add resource to json schema compiler: %w", err) + } + + schema, errSchema = compiler.Compile(schemaPath) + if errSchema != nil { + return nil, fmt.Errorf("failed to compile json schema: %w", err) + } + + return schema, nil +} From e0ebfdeb9f07f02dd4c20d0b6a07c611f3b9acea Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 13 Sep 2022 16:31:55 +0200 Subject: [PATCH 003/383] feat: wip validate --- cmd/cmdutil/cmdutil.go | 40 ++++++++++++++ cmd/validate.go | 33 +++++++---- cmd/validate/util.go | 123 +++++++++++++++++------------------------ go.mod | 2 +- go.sum | 2 + 5 files changed, 116 insertions(+), 84 deletions(-) create mode 100644 cmd/cmdutil/cmdutil.go diff --git a/cmd/cmdutil/cmdutil.go b/cmd/cmdutil/cmdutil.go new file mode 100644 index 000000000..1ee271635 --- /dev/null +++ b/cmd/cmdutil/cmdutil.go @@ -0,0 +1,40 @@ +package cmdutil + +import ( + "errors" + "log" + "os" + + "gopkg.in/yaml.v3" +) + +var ( + ErrNoOutputFormat = errors.New("output cannot be nil") + ErrNoConfigFileFound = errors.New("no config files found") + ErrUnknownOutputFormat = errors.New("unknown output format, supported ones are: json, text") + ErrTooManyArguments = errors.New("too many arguments") +) + +func GetWd() string { + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + return cwd +} + +func LoadConfig[T any](file string) T { + var conf T + + configData, err := os.ReadFile(file) + if err != nil { + log.Fatal(err) + } + + if err := yaml.Unmarshal(configData, &conf); err != nil { + log.Fatal(err) + } + + return conf +} diff --git a/cmd/validate.go b/cmd/validate.go index 6e12e9786..8fdd6bbd7 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -5,41 +5,54 @@ import ( "github.com/spf13/cobra" + "github.com/sighupio/furyctl/cmd/cmdutil" "github.com/sighupio/furyctl/cmd/validate" "github.com/sighupio/furyctl/internal/schema/santhosh" ) -var configPath string -var output string +var ErrSchemaDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") var validateCmd = &cobra.Command{ Use: "validate", Short: "Validate Furyfile", RunE: func(cmd *cobra.Command, args []string) error { - schemaLocation := "https://raw.githubusercontent.com/sighupio/fury-distribution/feature/create-draft-of-the-furyctl-yaml-json-schema" + distroLocation := cmd.Flag("distro-location").Value.String() - schema, err := santhosh.LoadSchema(schemaLocation) + schemasPath, err := validate.DownloadSchemas(distroLocation) if err != nil { - return fmt.Errorf("failed to load schema: %w", err) + return fmt.Errorf("%s: %w", ErrSchemaDownload, err) } hasErrors := error(nil) + furyctlFile, err := validate.ParseArgs(args) + if err != nil { + return err + } + + minimalConf := cmdutil.LoadConfig[validate.FuryctlConfig](furyctlFile) + + schemaPath := validate.GetSchemaPath(schemasPath, minimalConf) + + schema, err := santhosh.LoadSchema(schemaPath) + if err != nil { + return fmt.Errorf("failed to load schema: %w", err) + } + + conf := cmdutil.LoadConfig[any](furyctlFile) - conf := validate.LoadConfig[validate.FuryDistributionSpecs](configPath) if err := schema.ValidateInterface(conf); err != nil { - validate.PrintResults(output, err, conf, configPath) + validate.PrintResults(err, conf, furyctlFile) hasErrors = validate.ErrHasValidationErrors } - validate.PrintSummary(output, hasErrors != nil) + validate.PrintSummary(hasErrors != nil) return hasErrors }, } func init() { - validateCmd.Flags().StringP("config", "c", configPath, "Furyctl config file path") - validateCmd.Flags().StringP("output", "o", "text", "Output format (text|json)") + validateCmd.Flags().StringP("distro-location", "l", "", "Base URL used to download schemas.") rootCmd.AddCommand(validateCmd) } diff --git a/cmd/validate/util.go b/cmd/validate/util.go index ca03cb2d6..0961819ff 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -1,79 +1,86 @@ package validate import ( - "encoding/json" "errors" "fmt" - "os" + "path/filepath" + "strings" + "github.com/hashicorp/go-getter" log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" + "github.com/sighupio/furyctl/cmd/cmdutil" "github.com/sighupio/furyctl/internal/schema/santhosh" ) +const defaultSchemaBaseUrl = "git::https://git@github.com/sighupio/fury-distribution//schemas?ref=feature/create-draft-of-the-furyctl-yaml-json-schema" + var ( ErrHasValidationErrors = errors.New("schema has validation errors") ErrUnknownOutputFormat = errors.New("unknown output format") ) -type FuryDistributionSpecs struct { - Version string `json:"version"` - Kind string `json:"kind"` +type FuryctlConfig struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Spec struct { + DistributionVersion string `yaml:"distributionVersion"` + } `yaml:"spec"` +} + +func GetSchemaPath(basePath string, conf FuryctlConfig) string { + parts := strings.Split(conf.ApiVersion, "/") + foo := strings.Replace(parts[0], ".sighup.io", "", 1) + bar := parts[1] + filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind), foo, bar) + + return filepath.Join(basePath, conf.Spec.DistributionVersion, filename) } -func LoadConfig[T any](file string) T { - var conf T +func ParseArgs(args []string) (string, error) { + basePath := cmdutil.GetWd() - configData, err := os.ReadFile(file) - if err != nil { - log.Fatal(err) + furyctlFile := filepath.Join(basePath, "furyctl.yaml") + + if len(args) == 1 { + furyctlFile = args[0] } - if err := yaml.Unmarshal(configData, &conf); err != nil { - log.Fatal(err) + if len(args) > 1 { + return "", fmt.Errorf("%v, only 1 expected", cmdutil.ErrTooManyArguments) } - return conf + return furyctlFile, nil } -func PrintSummary(output string, hasErrors bool) { - switch output { - case "text": - if hasErrors { - fmt.Println("Validation failed") - } else { - fmt.Println("Validation succeeded") - } - - case "json": - if hasErrors { - fmt.Println("{\"result\":\"Validation failed\"}") - } else { - fmt.Println("{\"result\":\"Validation succeeded\"}") - } - - default: - log.Fatal(fmt.Errorf("'%s': %w", output, ErrUnknownOutputFormat)) +func DownloadSchemas(distroLocation string) (string, error) { + src := defaultSchemaBaseUrl + if distroLocation != "" { + src = distroLocation } -} -func PrintResults(output string, err error, conf any, configFile string) { - ptrPaths := santhosh.GetPtrPaths(err) + dst := "/tmp/fury-distribution/schemas" - switch output { - case "text": - printTextResults(ptrPaths, err, conf, configFile) + client := &getter.Client{ + Src: src, + Dst: dst, + Mode: getter.ClientModeDir, + } - case "json": - printJSONResults(ptrPaths, err, conf, configFile) + return dst, client.Get() +} - default: - log.Fatal(fmt.Errorf("'%s': %w", output, ErrUnknownOutputFormat)) +func PrintSummary(hasErrors bool) { + if hasErrors { + fmt.Println("Validation failed") + } else { + fmt.Println("Validation succeeded") } } -func printTextResults(ptrPaths [][]any, err error, conf any, configFile string) { +func PrintResults(err error, conf any, configFile string) { + ptrPaths := santhosh.GetPtrPaths(err) + fmt.Printf("CONFIG FILE %s\n", configFile) for _, path := range ptrPaths { @@ -91,33 +98,3 @@ func printTextResults(ptrPaths [][]any, err error, conf any, configFile string) fmt.Println(err) } - -func printJSONResults(ptrPaths [][]any, err error, conf any, configFile string) { - for _, path := range ptrPaths { - value, serr := santhosh.GetValueAtPath(conf, path) - if serr != nil { - log.Fatal(serr) - } - - jv, jerr := json.Marshal( - map[string]any{ - "file": configFile, - "message": "path contains an invalid configuration value", - "path": santhosh.JoinPtrPath(path), - "value": value, - }, - ) - if jerr != nil { - log.Fatal(jerr) - } - - fmt.Println(jv) - } - - jv, jerr := json.Marshal(err) - if jerr != nil { - log.Fatal(jerr) - } - - fmt.Println(jv) -} diff --git a/go.mod b/go.mod index c9c63363b..cd6c66389 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/gobuffalo/packd v1.0.2 // indirect github.com/gobuffalo/packr/v2 v2.8.3 github.com/hashicorp/go-checkpoint v0.5.0 - github.com/hashicorp/go-getter v1.6.1 + github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/go-version v1.3.0 github.com/hashicorp/terraform-exec v0.13.3 github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 749eb1e39..e95d7c9b2 100644 --- a/go.sum +++ b/go.sum @@ -219,6 +219,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI= github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY= github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= +github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk= +github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= From 8950bf5fc66786d9ee0dcd194dd4323c352e8276 Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 14 Sep 2022 09:43:24 +0200 Subject: [PATCH 004/383] chore: refactor command inits, add silencing of usage help text in case of error --- cmd/bootstrap.go | 1 - cmd/cluster.go | 1 - cmd/completion.go | 4 ---- cmd/root.go | 23 ++++++++++------------- cmd/template.go | 7 +++---- cmd/validate.go | 1 - cmd/version.go | 17 +++++++++++++++++ 7 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 cmd/version.go diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 97b9bf21e..8712960bd 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -224,5 +224,4 @@ func init() { bootstrapCmd.AddCommand(bootstrapApplyCmd) bootstrapCmd.AddCommand(bootstrapDestroyCmd) bootstrapCmd.AddCommand(bootstrapTemplateCmd) - rootCmd.AddCommand(bootstrapCmd) } diff --git a/cmd/cluster.go b/cmd/cluster.go index fb540a237..bea012501 100644 --- a/cmd/cluster.go +++ b/cmd/cluster.go @@ -225,5 +225,4 @@ func init() { clusterCmd.AddCommand(clusterApplyCmd) clusterCmd.AddCommand(clusterDestroyCmd) clusterCmd.AddCommand(clusterTemplateCmd) - rootCmd.AddCommand(clusterCmd) } diff --git a/cmd/completion.go b/cmd/completion.go index a88e266ba..02dd22c0d 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -68,7 +68,3 @@ PowerShell: } }, } - -func init() { - rootCmd.AddCommand(completionCmd) -} diff --git a/cmd/root.go b/cmd/root.go index bb872dd59..1b40f4a11 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -33,7 +33,12 @@ func Execute() error { } func init() { - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.furyctl.yaml)") + rootCmd.AddCommand(bootstrapCmd) + rootCmd.AddCommand(clusterCmd) + rootCmd.AddCommand(completionCmd) + rootCmd.AddCommand(templateCmd) + rootCmd.AddCommand(validateCmd) + rootCmd.AddCommand(vendorCmd) rootCmd.AddCommand(versionCmd) rootCmd.PersistentFlags().Bool("debug", false, "Enables furyctl debug output") @@ -54,13 +59,13 @@ func init() { s = spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(w)) }) + viper.AutomaticEnv() viper.SetEnvPrefix("furyctl") } func bootstrapLogrus(cmd *cobra.Command) { d, err := cmd.Flags().GetBool("debug") - if err != nil { logrus.Fatal(err) } @@ -68,8 +73,10 @@ func bootstrapLogrus(cmd *cobra.Command) { if d { logrus.SetLevel(logrus.DebugLevel) debug = true + return } + logrus.SetLevel(logrus.InfoLevel) } @@ -84,18 +91,8 @@ Furyctl is a simple CLI tool to: - download and manage the Kubernetes Fury Distribution (KFD) modules - create and manage Kubernetes Fury clusters `, + SilenceUsage: true, PersistentPreRun: func(cmd *cobra.Command, args []string) { bootstrapLogrus(cmd) }, } - -// versionCmd represents the version command -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Prints the client version information", - Long: ``, - Run: func(_ *cobra.Command, _ []string) { - logrus.Printf("Furyctl version %v\n", version) - logrus.Printf("built %v from commit %v", date, commit) - }, -} diff --git a/cmd/template.go b/cmd/template.go index 17a9b455b..758ffcee6 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -22,7 +22,7 @@ var ( tDryRun bool tNoOverwrite bool - TemplateCmd = &cobra.Command{ + templateCmd = &cobra.Command{ Use: "template", Short: "Renders the distribution's manifests from a template and a configuration file", Long: `Generates a folder with the Kustomization project for deploying Kubernetes Fury Distribution into a cluster. @@ -105,14 +105,13 @@ The generated folder will be created starting from a provided template and the p ) func init() { - rootCmd.AddCommand(TemplateCmd) - TemplateCmd.Flags().BoolVar( + templateCmd.Flags().BoolVar( &tDryRun, "dry-run", false, "Furyctl will try its best to generate the manifests despite the errors", ) - TemplateCmd.Flags().BoolVar( + templateCmd.Flags().BoolVar( &tNoOverwrite, "no-overwrite", false, diff --git a/cmd/validate.go b/cmd/validate.go index 8fdd6bbd7..93777389b 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -54,5 +54,4 @@ var validateCmd = &cobra.Command{ func init() { validateCmd.Flags().StringP("distro-location", "l", "", "Base URL used to download schemas.") - rootCmd.AddCommand(validateCmd) } diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..db7c42030 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Prints the client version information", + Long: ``, + Run: func(_ *cobra.Command, _ []string) { + logrus.Printf("Furyctl version %v\n", version) + logrus.Printf("built %v from commit %v", date, commit) + }, +} From afed1606718e071b0c4e64637baa5735bca811f3 Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 14 Sep 2022 14:47:05 +0200 Subject: [PATCH 005/383] chore: small commands refactoring --- cmd/init.go | 1 - cmd/root.go | 3 ++- cmd/template.go | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index 5140201bd..115a77500 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -21,7 +21,6 @@ var fileNames = [...]string{furyFile, kustomizationFile} var distributionVersion string func init() { - rootCmd.AddCommand(initCmd) initCmd.Flags().StringVar(&distributionVersion, "version", "", "Specify the Kubernetes Fury Distribution version") err := initCmd.MarkFlagRequired("version") if err != nil { diff --git a/cmd/root.go b/cmd/root.go index 1b40f4a11..71cee84ec 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -36,6 +36,7 @@ func init() { rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(clusterCmd) rootCmd.AddCommand(completionCmd) + rootCmd.AddCommand(initCmd) rootCmd.AddCommand(templateCmd) rootCmd.AddCommand(validateCmd) rootCmd.AddCommand(vendorCmd) @@ -92,7 +93,7 @@ Furyctl is a simple CLI tool to: - create and manage Kubernetes Fury clusters `, SilenceUsage: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRun: func(cmd *cobra.Command, _ []string) { bootstrapLogrus(cmd) }, } diff --git a/cmd/template.go b/cmd/template.go index 758ffcee6..06e4b6713 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -30,7 +30,6 @@ The generated folder will be created starting from a provided template and the p SilenceUsage: true, SilenceErrors: true, RunE: func(cmd *cobra.Command, args []string) error { - //TODO(rm-2470): To be reworked in redmine task - Define template command flags. source := "source" target := "target" From 84e0617f781425fcc0db8d23a5e96fe1a44243fe Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 14 Sep 2022 14:49:23 +0200 Subject: [PATCH 006/383] feat: add furyctl-defaults.yaml merging before schema validation --- cmd/validate.go | 99 ++++++++++++++++++++++---------- cmd/validate/util.go | 132 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 175 insertions(+), 56 deletions(-) diff --git a/cmd/validate.go b/cmd/validate.go index 93777389b..5b7ebd209 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -10,48 +10,85 @@ import ( "github.com/sighupio/furyctl/internal/schema/santhosh" ) -var ErrSchemaDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") +var ( + errSchemaDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") + errDefaultsDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") -var validateCmd = &cobra.Command{ - Use: "validate", - Short: "Validate Furyfile", - RunE: func(cmd *cobra.Command, args []string) error { - distroLocation := cmd.Flag("distro-location").Value.String() + validateCmd = &cobra.Command{ + Use: "validate", + Short: "Validate Furyfile", + RunE: func(cmd *cobra.Command, _ []string) error { + furyctlFilePath := cmd.Flag("config").Value.String() + schemasLocation := cmd.Flag("schemas-location").Value.String() + defaultsLocation := cmd.Flag("defaults-location").Value.String() - schemasPath, err := validate.DownloadSchemas(distroLocation) - if err != nil { - return fmt.Errorf("%s: %w", ErrSchemaDownload, err) - } + schemasPath, err := validate.DownloadFolder(schemasLocation, "schemas") + if err != nil { + return fmt.Errorf("%s: %w", errSchemaDownload, err) + } - hasErrors := error(nil) - furyctlFile, err := validate.ParseArgs(args) - if err != nil { - return err - } + defaultsPath, err := validate.DownloadFolder(defaultsLocation, "defaults") + if err != nil { + return fmt.Errorf("%s: %w", errDefaultsDownload, err) + } - minimalConf := cmdutil.LoadConfig[validate.FuryctlConfig](furyctlFile) + hasErrors := error(nil) - schemaPath := validate.GetSchemaPath(schemasPath, minimalConf) + minimalConf := cmdutil.LoadConfig[validate.FuryctlConfig](furyctlFilePath) - schema, err := santhosh.LoadSchema(schemaPath) - if err != nil { - return fmt.Errorf("failed to load schema: %w", err) - } + schemaPath := validate.GetSchemaPath(schemasPath, minimalConf) + defaultPath := validate.GetDefaultPath(defaultsPath, minimalConf) - conf := cmdutil.LoadConfig[any](furyctlFile) + defaultedFuryctlFilePath, err := validate.MergeConfigAndDefaults(furyctlFilePath, defaultPath) + if err != nil { + return fmt.Errorf("error merging config and defaults: %w", err) + } - if err := schema.ValidateInterface(conf); err != nil { - validate.PrintResults(err, conf, furyctlFile) + schema, err := santhosh.LoadSchema(schemaPath) + if err != nil { + return fmt.Errorf("failed to load schema: %w", err) + } - hasErrors = validate.ErrHasValidationErrors - } + conf := cmdutil.LoadConfig[any](defaultedFuryctlFilePath) - validate.PrintSummary(hasErrors != nil) + if err := schema.ValidateInterface(conf); err != nil { + validate.PrintResults(err, conf, defaultedFuryctlFilePath) - return hasErrors - }, -} + hasErrors = validate.ErrHasValidationErrors + } + + validate.PrintSummary(hasErrors != nil) + + return hasErrors + }, + } +) func init() { - validateCmd.Flags().StringP("distro-location", "l", "", "Base URL used to download schemas.") + validateCmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + validateCmd.Flags().StringP( + "schemas-location", + "", + "", + "Base URL used to download schemas. "+ + "It can either be a local path(eg: /path/to/fury/distribution//schemas) or "+ + "a remote URL(eg: https://git@github.com/sighupio/fury-distribution//schemas?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + validateCmd.Flags().StringP( + "defaults-location", + "", + "", + "Base URL used to download defaults. "+ + "It can either be a local path(eg: /path/to/fury/distribution//defaults) or "+ + "a remote URL(eg: https://git@github.com/sighupio/fury-distribution//defaults?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) } diff --git a/cmd/validate/util.go b/cmd/validate/util.go index 0961819ff..dfc8eb122 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -3,21 +3,29 @@ package validate import ( "errors" "fmt" + "os" "path/filepath" "strings" "github.com/hashicorp/go-getter" + "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" - "github.com/sighupio/furyctl/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/schema/santhosh" + yaml2 "github.com/sighupio/furyctl/internal/yaml" ) -const defaultSchemaBaseUrl = "git::https://git@github.com/sighupio/fury-distribution//schemas?ref=feature/create-draft-of-the-furyctl-yaml-json-schema" +const defaultBaseUrl = "https://git@github.com/sighupio/fury-distribution//%s?ref=feature/create-draft-of-the-furyctl-yaml-json-schema" var ( + downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} + ErrHasValidationErrors = errors.New("schema has validation errors") ErrUnknownOutputFormat = errors.New("unknown output format") + ErrDownloadingSchemas = errors.New("error downloading schemas") + ErrCreatingTempDir = errors.New("error creating temp dir") ) type FuryctlConfig struct { @@ -29,45 +37,81 @@ type FuryctlConfig struct { } func GetSchemaPath(basePath string, conf FuryctlConfig) string { - parts := strings.Split(conf.ApiVersion, "/") - foo := strings.Replace(parts[0], ".sighup.io", "", 1) - bar := parts[1] - filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind), foo, bar) + avp := strings.Split(conf.ApiVersion, "/") + ns := strings.Replace(avp[0], ".sighup.io", "", 1) + ver := avp[1] + filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind), ns, ver) return filepath.Join(basePath, conf.Spec.DistributionVersion, filename) } -func ParseArgs(args []string) (string, error) { - basePath := cmdutil.GetWd() - - furyctlFile := filepath.Join(basePath, "furyctl.yaml") +func GetDefaultPath(basePath string, conf FuryctlConfig) string { + return filepath.Join(basePath, conf.Spec.DistributionVersion, "furyctl-defaults.yaml") +} - if len(args) == 1 { - furyctlFile = args[0] +func DownloadFolder(distroLocation string, name string) (string, error) { + src := fmt.Sprintf(defaultBaseUrl, name) + if distroLocation != "" { + src = distroLocation } - if len(args) > 1 { - return "", fmt.Errorf("%v, only 1 expected", cmdutil.ErrTooManyArguments) + dir, err := os.MkdirTemp("", fmt.Sprintf("furyctl-%s-", name)) + if err != nil { + return "", fmt.Errorf("%s: %w", ErrCreatingTempDir, err) } - return furyctlFile, nil + logrus.Debugf("Downloading '%s' from '%s' in '%s'", name, src, dir) + + return dir, clientGet(src, dir) } -func DownloadSchemas(distroLocation string) (string, error) { - src := defaultSchemaBaseUrl - if distroLocation != "" { - src = distroLocation +func MergeConfigAndDefaults(furyctlFilePath string, defaultsFilePath string) (string, error) { + defaultsFile, err := yaml2.FromFile[map[any]any](defaultsFilePath) + if err != nil { + return "", err + } + + furyctlFile, err := yaml2.FromFile[map[any]any](furyctlFilePath) + if err != nil { + return "", err + } + + defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") + distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") + + merger := merge.NewMerger(defaultsModel, distributionModel) + + defaultedDistribution, err := merger.Merge() + if err != nil { + return "", err + } + + furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") + defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") + + merger2 := merge.NewMerger(furyctlModel, defaultedDistributionModel) + + defaultedFuryctl, err := merger2.Merge() + if err != nil { + return "", err + } + + outYaml, err := yaml.Marshal(defaultedFuryctl) + if err != nil { + return "", err } - dst := "/tmp/fury-distribution/schemas" + outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") + if err != nil { + return "", err + } - client := &getter.Client{ - Src: src, - Dst: dst, - Mode: getter.ClientModeDir, + confPath := filepath.Join(outDirPath, "config.yaml") + if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return "", err } - return dst, client.Get() + return confPath, nil } func PrintSummary(hasErrors bool) { @@ -98,3 +142,41 @@ func PrintResults(err error, conf any, configFile string) { fmt.Println(err) } + +// clientGet tries a few different protocols to get the source file or directory. +func clientGet(src, dst string) error { + protocols := []string{""} + if !urlHasForcedProtocol(src) { + protocols = downloadProtocols + } + + for _, protocol := range protocols { + fullSrc := fmt.Sprintf("%s%s", protocol, src) + + logrus.Debugf("Trying to download from: %s", fullSrc) + + client := &getter.Client{ + Src: fullSrc, + Dst: dst, + Mode: getter.ClientModeDir, + } + + if err := client.Get(); err == nil { + logrus.Debug("Download successful") + + return nil + } + } + + return ErrDownloadingSchemas +} + +func urlHasForcedProtocol(url string) bool { + for _, dp := range downloadProtocols { + if strings.HasPrefix(url, dp) { + return true + } + } + + return false +} From a4473cefe465211d6f6e45b9c068f1c0c4a80a5b Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 14 Sep 2022 15:43:26 +0200 Subject: [PATCH 007/383] chore: cleanup validation command - remove alias for logrus - restructure sentinel errors in validate cmd - simplify validation errors output - add tmp folders cleanup --- CONTRIBUTING.md | 6 +- cmd/bootstrap.go | 12 +- cmd/cluster.go | 12 +- cmd/cmdutil/cmdutil.go | 8 +- cmd/selfprovision.go | 8 +- cmd/validate.go | 22 +++- cmd/validate/util.go | 69 +++++------ internal/bootstrap/bootstrap.go | 68 +++++------ .../bootstrap/provisioners/aws/provisioner.go | 37 +++--- .../bootstrap/provisioners/gcp/provisioner.go | 41 ++++--- internal/cluster/cluster.go | 72 ++++++------ .../cluster/provisioners/eks/provisioner.go | 70 +++++------ .../cluster/provisioners/gke/provisioner.go | 36 +++--- .../provisioners/vsphere/provisioner.go | 59 +++++----- internal/configuration/config.go | 30 ++--- internal/configuration/templates.go | 4 +- internal/project/project.go | 12 +- internal/provisioners/provisioners.go | 8 +- internal/schema/santhosh/errors.go | 110 ------------------ internal/schema/santhosh/loader.go | 13 ++- pkg/analytics/analytics.go | 8 +- pkg/terraform/install.go | 28 ++--- pkg/terraform/terraform.go | 8 +- 23 files changed, 319 insertions(+), 422 deletions(-) delete mode 100644 internal/schema/santhosh/errors.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e15e49585..85fe9879e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,13 +76,13 @@ Then, add the `Dummy` configuration to the configuration parser in the `internal + dummySpec := bootstrapcfg.Dummy{} + err = yaml.Unmarshal(specBytes, &dummySpec) + if err != nil { -+ log.Errorf("error parsing configuration file: %v", err) ++ logrus.Errorf("error parsing configuration file: %v", err) + return err + } + config.Spec = dummySpec + return nil default: - log.Error("Error parsing the configuration file. Provisioner not found") + logrus.Error("Error parsing the configuration file. Provisioner not found") return errors.New("Bootstrap provisioner not found") ``` @@ -139,7 +139,7 @@ index 8c5a6d8..a3d6ecf 100644 + case config.Provisioner == "dummy": + return dummy.New(&config), nil default: - log.Error("Provisioner not found") + logrus.Error("Provisioner not found") return nil, errors.New("Provisioner not found") ``` diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 8712960bd..738bdb5f0 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -13,7 +13,7 @@ import ( "strings" "syscall" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -26,7 +26,7 @@ import ( func bPreDestroy(cmd *cobra.Command, args []string) (err error) { if bForce { - log.Warn("Force destroy of the bootstrap project") + logrus.Warn("Force destroy of the bootstrap project") return bPre(cmd, args) } fmt.Println("\r Are you sure you want to destroy it?\n Write 'yes' to continue") @@ -53,10 +53,10 @@ func bPre(cmd *cobra.Command, args []string) (err error) { bGitHubToken = viper.GetString("token") } - log.Debug("passing pre-flight checks") + logrus.Debug("passing pre-flight checks") err = parseConfig(bConfigFilePath, "Bootstrap") if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } wd, err := os.Getwd() @@ -64,7 +64,7 @@ func bPre(cmd *cobra.Command, args []string) (err error) { return err } workingDirFullPath := fmt.Sprintf("%v/%v", wd, bWorkingDir) - log.Debug("pre-flight checks ok!") + logrus.Debug("pre-flight checks ok!") prj = &project.Project{ Path: workingDirFullPath, } @@ -82,7 +82,7 @@ func bPre(cmd *cobra.Command, args []string) (err error) { } boot, err = bootstrap.New(bootstrapOpts) if err != nil { - log.Errorf("the bootstrap provisioner can not be initialized: %v", err) + logrus.Errorf("the bootstrap provisioner can not be initialized: %v", err) return err } return nil diff --git a/cmd/cluster.go b/cmd/cluster.go index bea012501..73ca4c521 100644 --- a/cmd/cluster.go +++ b/cmd/cluster.go @@ -18,14 +18,14 @@ import ( "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/pkg/analytics" "github.com/sighupio/furyctl/pkg/terraform" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) func cPreDestroy(cmd *cobra.Command, args []string) (err error) { if cForce { - log.Warn("Force destroy of the cluster project") + logrus.Warn("Force destroy of the cluster project") return cPre(cmd, args) } fmt.Println("\r Are you sure you want to destroy the cluster?\n Write 'yes' to continue") @@ -52,10 +52,10 @@ func cPre(cmd *cobra.Command, args []string) (err error) { cGitHubToken = viper.GetString("token") } - log.Debug("passing pre-flight checks") + logrus.Debug("passing pre-flight checks") err = parseConfig(cConfigFilePath, "Cluster") if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } wd, err := os.Getwd() @@ -63,7 +63,7 @@ func cPre(cmd *cobra.Command, args []string) (err error) { return err } workingDirFullPath := fmt.Sprintf("%v/%v", wd, cWorkingDir) - log.Debug("pre-flight checks ok!") + logrus.Debug("pre-flight checks ok!") prj = &project.Project{ Path: workingDirFullPath, } @@ -81,7 +81,7 @@ func cPre(cmd *cobra.Command, args []string) (err error) { } clu, err = cluster.New(clusterOpts) if err != nil { - log.Errorf("the cluster provisioner can not be initialized: %v", err) + logrus.Errorf("the cluster provisioner can not be initialized: %v", err) return err } return nil diff --git a/cmd/cmdutil/cmdutil.go b/cmd/cmdutil/cmdutil.go index 1ee271635..13bb566b2 100644 --- a/cmd/cmdutil/cmdutil.go +++ b/cmd/cmdutil/cmdutil.go @@ -2,9 +2,9 @@ package cmdutil import ( "errors" - "log" "os" + "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) @@ -18,7 +18,7 @@ var ( func GetWd() string { cwd, err := os.Getwd() if err != nil { - log.Fatal(err) + logrus.Fatal(err) } return cwd @@ -29,11 +29,11 @@ func LoadConfig[T any](file string) T { configData, err := os.ReadFile(file) if err != nil { - log.Fatal(err) + logrus.Fatal(err) } if err := yaml.Unmarshal(configData, &conf); err != nil { - log.Fatal(err) + logrus.Fatal(err) } return conf diff --git a/cmd/selfprovision.go b/cmd/selfprovision.go index c43b77be8..4dea39e66 100644 --- a/cmd/selfprovision.go +++ b/cmd/selfprovision.go @@ -13,7 +13,7 @@ import ( "github.com/sighupio/furyctl/internal/configuration" "github.com/sighupio/furyctl/internal/project" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) var ( @@ -24,15 +24,15 @@ var ( ) func parseConfig(path string, kind string) (err error) { - log.Debugf("parsing configuration file %v", path) + logrus.Debugf("parsing configuration file %v", path) cfg, err = configuration.Parse(path) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } if cfg.Kind != kind { errMessage := fmt.Sprintf("error parsing configuration file. Unexpected kind. Got: %v but: %v expected", cfg.Kind, kind) - log.Error(errMessage) + logrus.Error(errMessage) return errors.New(errMessage) } return nil diff --git a/cmd/validate.go b/cmd/validate.go index 5b7ebd209..a7cca6c37 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "path/filepath" "github.com/spf13/cobra" @@ -11,25 +12,31 @@ import ( ) var ( - errSchemaDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") errDefaultsDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") validateCmd = &cobra.Command{ Use: "validate", Short: "Validate Furyfile", RunE: func(cmd *cobra.Command, _ []string) error { + debug := cmd.Flag("debug").Value.String() == "true" furyctlFilePath := cmd.Flag("config").Value.String() schemasLocation := cmd.Flag("schemas-location").Value.String() defaultsLocation := cmd.Flag("defaults-location").Value.String() schemasPath, err := validate.DownloadFolder(schemasLocation, "schemas") if err != nil { - return fmt.Errorf("%s: %w", errSchemaDownload, err) + return err + } + if !debug { + defer validate.CleanupTempDir(filepath.Base(schemasPath)) } defaultsPath, err := validate.DownloadFolder(defaultsLocation, "defaults") if err != nil { - return fmt.Errorf("%s: %w", errDefaultsDownload, err) + return err + } + if !debug { + defer validate.CleanupTempDir(filepath.Base(defaultsPath)) } hasErrors := error(nil) @@ -41,18 +48,21 @@ var ( defaultedFuryctlFilePath, err := validate.MergeConfigAndDefaults(furyctlFilePath, defaultPath) if err != nil { - return fmt.Errorf("error merging config and defaults: %w", err) + return err + } + if !debug { + defer validate.CleanupTempDir(filepath.Base(defaultedFuryctlFilePath)) } schema, err := santhosh.LoadSchema(schemaPath) if err != nil { - return fmt.Errorf("failed to load schema: %w", err) + return err } conf := cmdutil.LoadConfig[any](defaultedFuryctlFilePath) if err := schema.ValidateInterface(conf); err != nil { - validate.PrintResults(err, conf, defaultedFuryctlFilePath) + validate.PrintResults(err, defaultedFuryctlFilePath) hasErrors = validate.ErrHasValidationErrors } diff --git a/cmd/validate/util.go b/cmd/validate/util.go index dfc8eb122..c4456119a 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -9,11 +9,9 @@ import ( "github.com/hashicorp/go-getter" "github.com/sirupsen/logrus" - log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" "github.com/sighupio/furyctl/internal/merge" - "github.com/sighupio/furyctl/internal/schema/santhosh" yaml2 "github.com/sighupio/furyctl/internal/yaml" ) @@ -22,10 +20,17 @@ const defaultBaseUrl = "https://git@github.com/sighupio/fury-distribution//%s?re var ( downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} + errDownloadOptionsExausted = errors.New("downloading options exausted") + + ErrCreatingTempDir = errors.New("error creating temp dir") + ErrDownloadingFolder = errors.New("error downloading folder") ErrHasValidationErrors = errors.New("schema has validation errors") + ErrMergeCompleteConfig = errors.New("error merging complete config") + ErrMergeDistroConfig = errors.New("error merging distribution config") ErrUnknownOutputFormat = errors.New("unknown output format") - ErrDownloadingSchemas = errors.New("error downloading schemas") - ErrCreatingTempDir = errors.New("error creating temp dir") + ErrWriteFile = errors.New("error writing file") + ErrYamlMarshalFile = errors.New("error marshaling yaml file") + ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) type FuryctlConfig struct { @@ -57,58 +62,62 @@ func DownloadFolder(distroLocation string, name string) (string, error) { dir, err := os.MkdirTemp("", fmt.Sprintf("furyctl-%s-", name)) if err != nil { - return "", fmt.Errorf("%s: %w", ErrCreatingTempDir, err) + return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) } logrus.Debugf("Downloading '%s' from '%s' in '%s'", name, src, dir) - return dir, clientGet(src, dir) + if err := clientGet(src, dir); err != nil { + return "", fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) + } + + return dir, nil } func MergeConfigAndDefaults(furyctlFilePath string, defaultsFilePath string) (string, error) { defaultsFile, err := yaml2.FromFile[map[any]any](defaultsFilePath) if err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) } furyctlFile, err := yaml2.FromFile[map[any]any](furyctlFilePath) if err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) } defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - merger := merge.NewMerger(defaultsModel, distributionModel) + distroMerger := merge.NewMerger(defaultsModel, distributionModel) - defaultedDistribution, err := merger.Merge() + defaultedDistribution, err := distroMerger.Merge() if err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrMergeDistroConfig, err) } furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") - merger2 := merge.NewMerger(furyctlModel, defaultedDistributionModel) + furyctlMerger := merge.NewMerger(furyctlModel, defaultedDistributionModel) - defaultedFuryctl, err := merger2.Merge() + defaultedFuryctl, err := furyctlMerger.Merge() if err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrMergeCompleteConfig, err) } outYaml, err := yaml.Marshal(defaultedFuryctl) if err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrYamlMarshalFile, err) } outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") if err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) } confPath := filepath.Join(outDirPath, "config.yaml") if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - return "", err + return "", fmt.Errorf("%w: %v", ErrWriteFile, err) } return confPath, nil @@ -122,25 +131,18 @@ func PrintSummary(hasErrors bool) { } } -func PrintResults(err error, conf any, configFile string) { - ptrPaths := santhosh.GetPtrPaths(err) - +func PrintResults(err error, configFile string) { fmt.Printf("CONFIG FILE %s\n", configFile) - for _, path := range ptrPaths { - value, serr := santhosh.GetValueAtPath(conf, path) - if serr != nil { - log.Fatal(serr) - } + fmt.Println(err) +} - fmt.Printf( - "path '%s' contains an invalid configuration value: %+v\n", - santhosh.JoinPtrPath(path), - value, - ) +func CleanupTempDir(dir string) { + if err := os.RemoveAll(dir); err != nil { + if !errors.Is(err, os.ErrNotExist) { + logrus.Error(err) + } } - - fmt.Println(err) } // clientGet tries a few different protocols to get the source file or directory. @@ -168,9 +170,10 @@ func clientGet(src, dst string) error { } } - return ErrDownloadingSchemas + return errDownloadOptionsExausted } +// urlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. func urlHasForcedProtocol(url string) bool { for _, dp := range downloadProtocols { if strings.HasPrefix(url, dp) { diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 4687ecaca..10a40ef07 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -16,7 +16,7 @@ import ( "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/internal/provisioners" "github.com/sighupio/furyctl/pkg/terraform" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) const initExecutorMessage = " Initializing the terraform executor" @@ -46,11 +46,11 @@ func New(opts *Options) (b *Bootstrap, err error) { // Grab the right provisioner p, err := provisioners.Get(*opts.ProvisionerConfiguration) if err != nil { - log.Errorf("error creating the bootstrap instance while acquiring the right provisioner: %v", err) + logrus.Errorf("Error creating the bootstrap instance while acquiring the right provisioner: %v", err) return nil, err } if p.Enterprise() && opts.TerraformOpts.GitHubToken == "" { - log.Warningf("The %v provisioner is an enterprise feature and requires a valid GitHub token", opts.ProvisionerConfiguration.Provisioner) + logrus.Warningf("The %v provisioner is an enterprise feature and requires a valid GitHub token", opts.ProvisionerConfiguration.Provisioner) } b = &Bootstrap{ s: opts.Spin, @@ -77,16 +77,16 @@ func (c *Bootstrap) Init(reset bool) (err error) { // Enterprise token validation if prov.Enterprise() && c.options.TerraformOpts.GitHubToken == "" { errorMsg := fmt.Sprintf("error while initiating the bootstap process. The %v provisioner is an enterprise feature and requires a valid GitHub token. Contact sales@sighup.io", c.options.ProvisionerConfiguration.Provisioner) - log.Error(errorMsg) + logrus.Error(errorMsg) return errors.New(errorMsg) } // Reset the project directory if reset { - log.Warn("Cleaning up the workdir") + logrus.Warn("Cleaning up the workdir") err = c.project.Reset() if err != nil { - log.Errorf("Error cleaning up the workdir") + logrus.Errorf("Error cleaning up the workdir") return err } } @@ -97,14 +97,14 @@ func (c *Bootstrap) Init(reset bool) (err error) { c.s.Start() err = c.project.CreateSubDirs(bootstrapProjectDefaultSubDirs) if err != nil { - log.Errorf("error while initializing project subdirectories: %v", err) + logrus.Errorf("error while initializing project subdirectories: %v", err) return err } // .gitignore and .gitattributes err = c.createGitFiles() if err != nil { - log.Errorf("error while initializing project git files: %v", err) + logrus.Errorf("error while initializing project git files: %v", err) return err } @@ -115,7 +115,7 @@ func (c *Bootstrap) Init(reset bool) (err error) { err = c.initTerraformExecutor() if err != nil { - log.Errorf("error while initializing terraform executor: %v", err) + logrus.Errorf("error while initializing terraform executor: %v", err) return err } @@ -125,7 +125,7 @@ func (c *Bootstrap) Init(reset bool) (err error) { c.s.Start() err = c.installProvisionerTerraformFiles() if err != nil { - log.Errorf("error while copying terraform project from the provisioner to the project dir: %v", err) + logrus.Errorf("error while copying terraform project from the provisioner to the project dir: %v", err) return err } @@ -137,7 +137,7 @@ func (c *Bootstrap) Init(reset bool) (err error) { err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) if err != nil { - log.Errorf("error while running terraform init in the project dir: %v", err) + logrus.Errorf("error while running terraform init in the project dir: %v", err) return err } c.s.Stop() @@ -222,13 +222,13 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { c.s.Start() err = c.project.CreateSubDirs(bootstrapProjectDefaultSubDirs) if err != nil { - log.Warnf("error while updating project subdirectories: %v", err) + logrus.Warnf("error while updating project subdirectories: %v", err) } // .gitignore and .gitattributes err = c.createGitFiles() if err != nil { - log.Errorf("error while initializing project git files: %v", err) + logrus.Errorf("error while initializing project git files: %v", err) return err } @@ -237,7 +237,7 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { c.s.Start() err = c.initTerraformExecutor() if err != nil { - log.Errorf("Error while initializing the terraform executor: %v", err) + logrus.Errorf("Error while initializing the terraform executor: %v", err) return err } @@ -247,7 +247,7 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { c.s.Start() err = c.installProvisionerTerraformFiles() if err != nil { - log.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) + logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) } prov := *c.provisioner @@ -260,7 +260,7 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend), tfexec.Upgrade(c.options.TerraformOpts.UpgradeDeps)) if err != nil { - log.Errorf("error while running terraform init in the project dir: %v", err) + logrus.Errorf("error while running terraform init in the project dir: %v", err) return err } @@ -269,7 +269,7 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { c.s.Start() _, err = prov.Update() if err != nil { - log.Errorf("Error while updating the bootstrap. Take a look to the logs. %v", err) + logrus.Errorf("Error while updating the bootstrap. Take a look to the logs. %v", err) return err } c.s.Stop() @@ -278,14 +278,14 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { var output []byte output, err = c.output() if err != nil { - log.Errorf("Error while getting the output with the bootstrap data: %v", err) + logrus.Errorf("Error while getting the output with the bootstrap data: %v", err) return err } proj := *c.project err = proj.WriteFile("output/output.json", output) if err != nil { - log.Errorf("Error while writing the output.json to the project directory: %v", err) + logrus.Errorf("Error while writing the output.json to the project directory: %v", err) return err } c.s.Stop() @@ -295,12 +295,12 @@ func (c *Bootstrap) Update(dryrun bool) (err error) { c.s.Start() err = prov.Plan() if err != nil { - log.Errorf("[DRYRUN] Error while updating the bootstrap. Take a look to the logs. %v", err) + logrus.Errorf("[DRYRUN] Error while updating the bootstrap. Take a look to the logs. %v", err) return err } c.s.Stop() proj := *c.project - log.Infof("[DRYRUN] Discover the resulting plan in the %v/logs/terraform.logs file", proj.Path) + logrus.Infof("[DRYRUN] Discover the resulting plan in the %v/logs/terraform.logs file", proj.Path) c.postPlan() } return nil @@ -314,13 +314,13 @@ func (c *Bootstrap) Destroy() (err error) { c.s.Start() err = c.project.CreateSubDirs(bootstrapProjectDefaultSubDirs) if err != nil { - log.Warnf("error while updating project subdirectories: %v", err) + logrus.Warnf("error while updating project subdirectories: %v", err) } // .gitignore and .gitattributes err = c.createGitFiles() if err != nil { - log.Errorf("error while initializing project git files: %v", err) + logrus.Errorf("error while initializing project git files: %v", err) return err } @@ -329,7 +329,7 @@ func (c *Bootstrap) Destroy() (err error) { c.s.Start() err = c.initTerraformExecutor() if err != nil { - log.Errorf("Error while initializing the terraform executor: %v", err) + logrus.Errorf("Error while initializing the terraform executor: %v", err) return err } @@ -339,7 +339,7 @@ func (c *Bootstrap) Destroy() (err error) { c.s.Start() err = c.installProvisionerTerraformFiles() if err != nil { - log.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) + logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) } prov := *c.provisioner @@ -352,7 +352,7 @@ func (c *Bootstrap) Destroy() (err error) { err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) if err != nil { - log.Errorf("error while running terraform init in the project dir: %v", err) + logrus.Errorf("error while running terraform init in the project dir: %v", err) return err } @@ -361,7 +361,7 @@ func (c *Bootstrap) Destroy() (err error) { c.s.Start() err = prov.Destroy() if err != nil { - log.Errorf("Error while destroying the bootstrap. Take a look to the logs. %v", err) + logrus.Errorf("Error while destroying the bootstrap. Take a look to the logs. %v", err) return err } c.s.Stop() @@ -377,12 +377,12 @@ func (c *Bootstrap) installProvisionerTerraformFiles() (err error) { for _, tfFileName := range prov.TerraformFiles() { tfFile, err := b.Find(tfFileName) if err != nil { - log.Errorf("Error while finding the right file in the box: %v", err) + logrus.Errorf("Error while finding the right file in the box: %v", err) return err } err = proj.WriteFile(tfFileName, tfFile) if err != nil { - log.Errorf("Error while writing the binary data from the box to the project dir: %v", err) + logrus.Errorf("Error while writing the binary data from the box to the project dir: %v", err) return err } } @@ -393,7 +393,7 @@ func (c *Bootstrap) installProvisionerTerraformFiles() (err error) { func (c *Bootstrap) initTerraformExecutor() (err error) { tf, err := terraform.NewExecutor(*c.options.TerraformOpts) if err != nil { - log.Errorf("Error while initializing the terraform executor: %v", err) + logrus.Errorf("Error while initializing the terraform executor: %v", err) return err } @@ -406,11 +406,11 @@ func (c *Bootstrap) initTerraformExecutor() (err error) { // Output gathers the Output in form of binary data func (c *Bootstrap) output() ([]byte, error) { prov := *c.provisioner - log.Info("Gathering output file as json") + logrus.Info("Gathering output file as json") var output map[string]tfexec.OutputMeta output, err := prov.TerraformExecutor().Output(context.Background()) if err != nil { - log.Fatalf("Error while getting project output: %v", err) + logrus.Fatalf("Error while getting project output: %v", err) return nil, err } return json.MarshalIndent(output, "", " ") @@ -428,7 +428,7 @@ func (c *Bootstrap) createGitFiles() error { ` err := c.project.WriteFile(".gitattributes", []byte(gitattributes)) if err != nil { - log.Errorf("error while creating .gitattributes: %v", err) + logrus.Errorf("error while creating .gitattributes: %v", err) return err } @@ -440,7 +440,7 @@ bin ` err = c.project.WriteFile(".gitignore", []byte(gitignore)) if err != nil { - log.Errorf("error while creating .gitignore: %v", err) + logrus.Errorf("error while creating .gitignore: %v", err) return err } diff --git a/internal/bootstrap/provisioners/aws/provisioner.go b/internal/bootstrap/provisioners/aws/provisioner.go index 8b2e5d6b9..650ec08e6 100644 --- a/internal/bootstrap/provisioners/aws/provisioner.go +++ b/internal/bootstrap/provisioners/aws/provisioner.go @@ -16,8 +16,7 @@ import ( "github.com/hashicorp/terraform-exec/tfexec" cfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" "github.com/sighupio/furyctl/internal/configuration" - - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) // InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated @@ -39,7 +38,7 @@ func (d *AWS) UpdateMessage() string { var output map[string]tfexec.OutputMeta output, err := d.terraform.Output(context.Background()) if err != nil { - log.Error("Can not get output values") + logrus.Error("Can not get output values") } spec := d.config.Spec.(cfg.AWS) sshUsers := spec.VPN.SSHUsers @@ -47,23 +46,23 @@ func (d *AWS) UpdateMessage() string { var vpnInstanceIPs, publicSubnetsIDs, privateSubnetsIDs []string err = json.Unmarshal(output["vpn_ip"].Value, &vpnInstanceIPs) if err != nil { - log.Error("Can not get `vpn_ip` value") + logrus.Error("Can not get `vpn_ip` value") } err = json.Unmarshal(output["vpn_operator_name"].Value, &vpnOperatorName) if err != nil { - log.Error("Can not get `vpn_operator_name` value") + logrus.Error("Can not get `vpn_operator_name` value") } err = json.Unmarshal(output["vpc_id"].Value, &vpcID) if err != nil { - log.Error("Can not get `vpc_id` value") + logrus.Error("Can not get `vpc_id` value") } err = json.Unmarshal(output["public_subnets"].Value, &publicSubnetsIDs) if err != nil { - log.Error("Can not get `public_subnets` value") + logrus.Error("Can not get `public_subnets` value") } err = json.Unmarshal(output["private_subnets"].Value, &privateSubnetsIDs) if err != nil { - log.Error("Can not get `private_subnets` value") + logrus.Error("Can not get `private_subnets` value") } vpnFragment := "" @@ -222,29 +221,29 @@ func (d AWS) Prepare() (err error) { // Plan runs a dry run execution func (d AWS) Plan() (err error) { - log.Info("[DRYRUN] Updating AWS Bootstrap project") + logrus.Info("[DRYRUN] Updating AWS Bootstrap project") err = d.createVarFile() if err != nil { return err } changes, err := d.terraform.Plan(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) if err != nil { - log.Fatalf("[DRYRUN] Something went wrong while updating aws. %v", err) + logrus.Fatalf("[DRYRUN] Something went wrong while updating aws. %v", err) return err } if changes { - log.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") + logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") } else { - log.Info("[DRYRUN] Everything is up to date") + logrus.Info("[DRYRUN] Everything is up to date") } - log.Info("[DRYRUN] AWS Updated") + logrus.Info("[DRYRUN] AWS Updated") return nil } // Update runs terraform apply in the project func (d AWS) Update() (string, error) { - log.Info("Updating AWS Bootstrap project") + logrus.Info("Updating AWS Bootstrap project") err := d.createVarFile() if err != nil { return "", err @@ -252,17 +251,17 @@ func (d AWS) Update() (string, error) { err = d.terraform.Apply(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) if err != nil { - log.Fatalf("Something went wrong while updating aws. %v", err) + logrus.Fatalf("Something went wrong while updating aws. %v", err) return "", err } - log.Info("AWS Updated") + logrus.Info("AWS Updated") return "", nil } // Destroy runs terraform destroy in the project func (d AWS) Destroy() (err error) { - log.Info("Destroying AWS Bootstrap project") + logrus.Info("Destroying AWS Bootstrap project") err = d.createVarFile() if err != nil { return err @@ -270,9 +269,9 @@ func (d AWS) Destroy() (err error) { err = d.terraform.Destroy(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) if err != nil { - log.Fatalf("Something went wrong while destroying AWS Bootstrap project. %v", err) + logrus.Fatalf("Something went wrong while destroying AWS Bootstrap project. %v", err) return err } - log.Info("AWS Bootstrap destroyed") + logrus.Info("AWS Bootstrap destroyed") return nil } diff --git a/internal/bootstrap/provisioners/gcp/provisioner.go b/internal/bootstrap/provisioners/gcp/provisioner.go index 05980e5a1..c45e1fafd 100644 --- a/internal/bootstrap/provisioners/gcp/provisioner.go +++ b/internal/bootstrap/provisioners/gcp/provisioner.go @@ -16,8 +16,7 @@ import ( "github.com/hashicorp/terraform-exec/tfexec" cfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" "github.com/sighupio/furyctl/internal/configuration" - - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) // InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated @@ -39,7 +38,7 @@ func (d *GCP) UpdateMessage() string { var output map[string]tfexec.OutputMeta output, err := d.terraform.Output(context.Background()) if err != nil { - log.Error("Can not get output values") + logrus.Error("Can not get output values") } spec := d.config.Spec.(cfg.GCP) sshUsers := spec.VPN.SSHUsers @@ -52,31 +51,31 @@ func (d *GCP) UpdateMessage() string { err = json.Unmarshal(output["vpn_ip"].Value, &vpnInstanceIPs) if err != nil { - log.Error("Can not get `vpn_ip` value") + logrus.Error("Can not get `vpn_ip` value") } err = json.Unmarshal(output["vpn_operator_name"].Value, &vpnOperatorName) if err != nil { - log.Error("Can not get `vpn_operator_name` value") + logrus.Error("Can not get `vpn_operator_name` value") } err = json.Unmarshal(output["network_name"].Value, &networkName) if err != nil { - log.Error("Can not get `network_name` value") + logrus.Error("Can not get `network_name` value") } err = json.Unmarshal(output["public_subnets"].Value, &publicSubnetsIDs) if err != nil { - log.Error("Can not get `public_subnets` value") + logrus.Error("Can not get `public_subnets` value") } err = json.Unmarshal(output["private_subnets"].Value, &privateSubnetsIDs) if err != nil { - log.Error("Can not get `private_subnets` value") + logrus.Error("Can not get `private_subnets` value") } err = json.Unmarshal(output["cluster_subnet"].Value, &clusterSubnet) if err != nil { - log.Error("Can not get `cluster_subnet` value") + logrus.Error("Can not get `cluster_subnet` value") } err = json.Unmarshal(output["additional_cluster_subnet"].Value, &additionalClusterSubnet) if err != nil { - log.Error("Can not get `additional_cluster_subnet` value") + logrus.Error("Can not get `additional_cluster_subnet` value") } for _, subnet := range additionalClusterSubnet { @@ -251,23 +250,23 @@ func (d GCP) TerraformFiles() []string { // Plan runs a dry run execution func (d GCP) Plan() (err error) { - log.Info("[DRYRUN] Updating GCP Bootstrap project") + logrus.Info("[DRYRUN] Updating GCP Bootstrap project") err = d.createVarFile() if err != nil { return err } changes, err := d.terraform.Plan(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) if err != nil { - log.Fatalf("[DRYRUN] Something went wrong while updating gcp. %v", err) + logrus.Fatalf("[DRYRUN] Something went wrong while updating gcp. %v", err) return err } if changes { - log.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") + logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") } else { - log.Info("[DRYRUN] Everything is up to date") + logrus.Info("[DRYRUN] Everything is up to date") } - log.Info("[DRYRUN] GCP Updated") + logrus.Info("[DRYRUN] GCP Updated") return nil } @@ -277,7 +276,7 @@ func (d GCP) Prepare() (err error) { // Update runs terraform apply in the project func (d GCP) Update() (string, error) { - log.Info("Updating GCP Bootstrap project") + logrus.Info("Updating GCP Bootstrap project") err := d.createVarFile() if err != nil { return "", err @@ -285,17 +284,17 @@ func (d GCP) Update() (string, error) { err = d.terraform.Apply(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) if err != nil { - log.Fatalf("Something went wrong while updating gcp. %v", err) + logrus.Fatalf("Something went wrong while updating gcp. %v", err) return "", err } - log.Info("GCP Updated") + logrus.Info("GCP Updated") return "", nil } // Destroy runs terraform destroy in the project func (d GCP) Destroy() (err error) { - log.Info("Destroying GCP Bootstrap project") + logrus.Info("Destroying GCP Bootstrap project") err = d.createVarFile() if err != nil { return err @@ -303,9 +302,9 @@ func (d GCP) Destroy() (err error) { err = d.terraform.Destroy(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) if err != nil { - log.Fatalf("Something went wrong while destroying GCP Bootstrap project. %v", err) + logrus.Fatalf("Something went wrong while destroying GCP Bootstrap project. %v", err) return err } - log.Info("GCP Bootstrap destroyed") + logrus.Info("GCP Bootstrap destroyed") return nil } diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index 965998d1e..9d86b9842 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -16,7 +16,7 @@ import ( "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/internal/provisioners" "github.com/sighupio/furyctl/pkg/terraform" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) const initExecutorMessage = " Initializing the terraform executor" @@ -46,11 +46,11 @@ func New(opts *Options) (c *Cluster, err error) { // Grab the right provisioner p, err := provisioners.Get(*opts.ProvisionerConfiguration) if err != nil { - log.Errorf("error creating the cluster instance while acquiring the right provisioner: %v", err) + logrus.Errorf("Error creating the cluster instance while acquiring the right provisioner: %v", err) return nil, err } if p.Enterprise() && opts.TerraformOpts.GitHubToken == "" { - log.Warningf("The %v provisioner is an enterprise feature and requires a valid GitHub token", opts.ProvisionerConfiguration.Provisioner) + logrus.Warningf("The %v provisioner is an enterprise feature and requires a valid GitHub token", opts.ProvisionerConfiguration.Provisioner) } c = &Cluster{ s: opts.Spin, @@ -78,16 +78,16 @@ func (c *Cluster) Init(reset bool) (err error) { // Enterprise token validation if prov.Enterprise() && c.options.TerraformOpts.GitHubToken == "" { errorMsg := fmt.Sprintf("error creating the cluster instance. The %v provisioner is an enterprise feature and requires a valid GitHub token. Contact sales@sighup.io", c.options.ProvisionerConfiguration.Provisioner) - log.Error(errorMsg) + logrus.Error(errorMsg) return errors.New(errorMsg) } // Reset the project directory if reset { - log.Warn("Cleaning up the workdir") + logrus.Warn("Cleaning up the workdir") err = c.project.Reset() if err != nil { - log.Errorf("Error cleaning up the workdir") + logrus.Errorf("Error cleaning up the workdir") return err } } @@ -98,14 +98,14 @@ func (c *Cluster) Init(reset bool) (err error) { c.s.Start() err = c.project.CreateSubDirs(clusterProjectDefaultSubDirs) if err != nil { - log.Errorf("error while initializing project subdirectories: %v", err) + logrus.Errorf("error while initializing project subdirectories: %v", err) return err } // .gitignore and .gitattributes err = c.createGitFiles() if err != nil { - log.Errorf("error while initializing project git files: %v", err) + logrus.Errorf("error while initializing project git files: %v", err) return err } @@ -116,7 +116,7 @@ func (c *Cluster) Init(reset bool) (err error) { err = c.initTerraformExecutor() if err != nil { - log.Errorf("error while initializing terraform executor: %v", err) + logrus.Errorf("error while initializing terraform executor: %v", err) return err } @@ -126,7 +126,7 @@ func (c *Cluster) Init(reset bool) (err error) { c.s.Start() err = c.installProvisionerTerraformFiles() if err != nil { - log.Errorf("error while copying terraform project from the provisioner to the project dir: %v", err) + logrus.Errorf("error while copying terraform project from the provisioner to the project dir: %v", err) return err } @@ -136,7 +136,7 @@ func (c *Cluster) Init(reset bool) (err error) { c.s.Start() err = prov.Prepare() if err != nil { - log.Errorf("error while preparing provisioner environment: %v", err) + logrus.Errorf("error while preparing provisioner environment: %v", err) return err } @@ -148,7 +148,7 @@ func (c *Cluster) Init(reset bool) (err error) { err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) if err != nil { - log.Errorf("error while running terraform init in the project dir: %v", err) + logrus.Errorf("error while running terraform init in the project dir: %v", err) return err } c.s.Stop() @@ -238,13 +238,13 @@ func (c *Cluster) Update(dryrun bool) (err error) { c.s.Start() err = c.project.CreateSubDirs(clusterProjectDefaultSubDirs) if err != nil { - log.Warnf("error while initializing project subdirectories: %v", err) + logrus.Warnf("error while initializing project subdirectories: %v", err) } // .gitignore and .gitattributes err = c.createGitFiles() if err != nil { - log.Errorf("error while initializing project git files: %v", err) + logrus.Errorf("error while initializing project git files: %v", err) return err } @@ -253,7 +253,7 @@ func (c *Cluster) Update(dryrun bool) (err error) { c.s.Start() err = c.initTerraformExecutor() if err != nil { - log.Errorf("Error while initializing the terraform executor: %v", err) + logrus.Errorf("Error while initializing the terraform executor: %v", err) return err } @@ -263,7 +263,7 @@ func (c *Cluster) Update(dryrun bool) (err error) { c.s.Start() err = c.installProvisionerTerraformFiles() if err != nil { - log.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) + logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) } // Init the terraform project @@ -275,7 +275,7 @@ func (c *Cluster) Update(dryrun bool) (err error) { err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend), tfexec.Upgrade(c.options.TerraformOpts.UpgradeDeps)) if err != nil { - log.Errorf("error while running terraform init in the project dir: %v", err) + logrus.Errorf("Error while running terraform init in the project dir: %v", err) return err } @@ -286,7 +286,7 @@ func (c *Cluster) Update(dryrun bool) (err error) { c.s.Start() kubeconfig, err := prov.Update() if err != nil { - log.Errorf("Error while updating the cluster. Take a look to the logs. %v", err) + logrus.Errorf("Error while updating the cluster. Take a look to the logs. %v", err) return err } c.s.Stop() @@ -295,21 +295,21 @@ func (c *Cluster) Update(dryrun bool) (err error) { var output []byte output, err = c.output() if err != nil { - log.Errorf("Error while getting the output with the cluster data: %v", err) + logrus.Errorf("Error while getting the output with the cluster data: %v", err) return err } proj := *c.project err = proj.WriteFile("output/output.json", output) if err != nil { - log.Errorf("Error while writing the output.json to the project directory: %v", err) + logrus.Errorf("Error while writing the output.json to the project directory: %v", err) return err } c.s.Stop() err = proj.WriteFile("secrets/kubeconfig", []byte(kubeconfig)) if err != nil { - log.Errorf("Error while writing the kubeconfig to the project directory: %v", err) + logrus.Errorf("Error while writing the kubeconfig to the project directory: %v", err) return err } c.s.Stop() @@ -320,12 +320,12 @@ func (c *Cluster) Update(dryrun bool) (err error) { c.s.Start() err = prov.Plan() if err != nil { - log.Errorf("[DRYRUN] Error while updating the cluster. Take a look to the logs. %v", err) + logrus.Errorf("[DRYRUN] Error while updating the cluster. Take a look to the logs. %v", err) return err } c.s.Stop() proj := *c.project - log.Infof("[DRYRUN] Discover the resulting plan in the %v/logs/terraform.logs file", proj.Path) + logrus.Infof("[DRYRUN] Discover the resulting plan in the %v/logs/terraform.logs file", proj.Path) c.postPlan() } return nil @@ -339,13 +339,13 @@ func (c *Cluster) Destroy() (err error) { c.s.Start() err = c.project.CreateSubDirs(clusterProjectDefaultSubDirs) if err != nil { - log.Warnf("error while initializing project subdirectories: %v", err) + logrus.Warnf("Error while initializing project subdirectories: %v", err) } // .gitignore and .gitattributes err = c.createGitFiles() if err != nil { - log.Errorf("error while initializing project git files: %v", err) + logrus.Errorf("Error while initializing project git files: %v", err) return err } @@ -354,7 +354,7 @@ func (c *Cluster) Destroy() (err error) { c.s.Start() err = c.initTerraformExecutor() if err != nil { - log.Errorf("Error while initializing the terraform executor: %v", err) + logrus.Errorf("Error while initializing the terraform executor: %v", err) return err } @@ -364,7 +364,7 @@ func (c *Cluster) Destroy() (err error) { c.s.Start() err = c.installProvisionerTerraformFiles() if err != nil { - log.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) + logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) } // Init the terraform project @@ -376,7 +376,7 @@ func (c *Cluster) Destroy() (err error) { err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) if err != nil { - log.Errorf("error while running terraform init in the project dir: %v", err) + logrus.Errorf("error while running terraform init in the project dir: %v", err) return err } @@ -385,7 +385,7 @@ func (c *Cluster) Destroy() (err error) { c.s.Start() err = prov.Destroy() if err != nil { - log.Errorf("Error while destroying the cluster. Take a look to the logs. %v", err) + logrus.Errorf("Error while destroying the cluster. Take a look to the logs. %v", err) return err } c.s.Stop() @@ -401,12 +401,12 @@ func (c *Cluster) installProvisionerTerraformFiles() (err error) { for _, tfFileName := range prov.TerraformFiles() { tfFile, err := b.Find(tfFileName) if err != nil { - log.Errorf("Error while finding the right file in the box: %v", err) + logrus.Errorf("Error while finding the right file in the box: %v", err) return err } err = proj.WriteFile(tfFileName, tfFile) if err != nil { - log.Errorf("Error while writing the binary data from the box to the project dir: %v", err) + logrus.Errorf("Error while writing the binary data from the box to the project dir: %v", err) return err } } @@ -417,7 +417,7 @@ func (c *Cluster) installProvisionerTerraformFiles() (err error) { func (c *Cluster) initTerraformExecutor() (err error) { tf, err := terraform.NewExecutor(*c.options.TerraformOpts) if err != nil { - log.Errorf("Error while initializing the terraform executor: %v", err) + logrus.Errorf("Error while initializing the terraform executor: %v", err) return err } @@ -430,11 +430,11 @@ func (c *Cluster) initTerraformExecutor() (err error) { // Output gathers the Output in form of binary data func (c *Cluster) output() ([]byte, error) { prov := *c.provisioner - log.Info("Gathering output file as json") + logrus.Info("Gathering output file as json") var output map[string]tfexec.OutputMeta output, err := prov.TerraformExecutor().Output(context.Background()) if err != nil { - log.Fatalf("Error while getting project output: %v", err) + logrus.Fatalf("Error while getting project output: %v", err) return nil, err } return json.MarshalIndent(output, "", " ") @@ -452,7 +452,7 @@ func (c *Cluster) createGitFiles() error { ` err := c.project.WriteFile(".gitattributes", []byte(gitattributes)) if err != nil { - log.Errorf("error while creating .gitattributes: %v", err) + logrus.Errorf("error while creating .gitattributes: %v", err) return err } @@ -464,7 +464,7 @@ bin ` err = c.project.WriteFile(".gitignore", []byte(gitignore)) if err != nil { - log.Errorf("error while creating .gitignore: %v", err) + logrus.Errorf("error while creating .gitignore: %v", err) return err } diff --git a/internal/cluster/provisioners/eks/provisioner.go b/internal/cluster/provisioners/eks/provisioner.go index 13e7cbc19..9062bf550 100644 --- a/internal/cluster/provisioners/eks/provisioner.go +++ b/internal/cluster/provisioners/eks/provisioner.go @@ -14,7 +14,7 @@ import ( "github.com/gobuffalo/packr/v2" "github.com/hashicorp/terraform-exec/tfexec" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" cfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sighupio/furyctl/internal/configuration" @@ -22,12 +22,12 @@ import ( // InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated func (e *EKS) InitMessage() string { - return `Kubernetes Fury EKS + return `[EKS] Fury -This provisioner creates a battle-tested Kubernetes Fury Cluster based on AWS EKS -with a private control plane and a production-grade setup. +This provisioner creates a battle-tested AWS EKS Kubernetes Cluster +with a private and production-grade setup. -Requires network connectivity to the target VPC (like a VPN connection) to deploy the cluster. +Requires to connect to a VPN server to deploy the cluster from this computer. Use a bastion host (inside the EKS VPC) as an alternative method to deploy the cluster. The provisioner requires the following software installed: @@ -43,36 +43,34 @@ func (e *EKS) UpdateMessage() string { var output map[string]tfexec.OutputMeta output, err := e.terraform.Output(context.Background()) if err != nil { - log.Error("Can not get output values") + logrus.Error("Can not get output values") } var clusterEndpoint, clusterOperatorName string err = json.Unmarshal(output["cluster_endpoint"].Value, &clusterEndpoint) if err != nil { - log.Error("Can not get `cluster_endpoint` value") + logrus.Error("Can not get `cluster_endpoint` value") } err = json.Unmarshal(output["operator_ssh_user"].Value, &clusterOperatorName) if err != nil { - log.Error("Can not get `operator_ssh_user` value") + logrus.Error("Can not get `operator_ssh_user` value") } return fmt.Sprintf( - `Kubernetes Fury EKS + `[EKS] Fury All the cluster components are up to date. - -Kubernetes Fury EKS cluster is ready. +EKS Kubernetes cluster ready. EKS Cluster Endpoint: %v SSH Operator Name: %v Use the ssh %v username to access the EKS instances with the configured SSH key. - Discover the instances by running $ kubectl get nodes -Then access them by running: +Then access by running: -$ ssh %v@ +$ ssh %v@node-name-reported-by-kubectl-get-nodes `, clusterEndpoint, clusterOperatorName, clusterOperatorName, clusterOperatorName, ) @@ -80,13 +78,11 @@ $ ssh %v@ // DestroyMessage return a custom provisioner message the user will see once the cluster is destroyed func (e *EKS) DestroyMessage() string { - return `Kubernetes Fury EKS - + return `[EKS] Fury All cluster components were destroyed. - EKS control plane and workers went away. -If you faced any problems, please contact support if you have a subscription or write us to sales@sighup.io. +Had problems, contact us at sales@sighup.io. ` } @@ -112,7 +108,7 @@ func (e EKS) createVarFile() (err error) { buffer.WriteString(fmt.Sprintf("cluster_name = \"%v\"\n", e.config.Metadata.Name)) buffer.WriteString(fmt.Sprintf("cluster_version = \"%v\"\n", spec.Version)) if spec.LogRetentionDays != 0 { - buffer.WriteString(fmt.Sprintf("cluster_log_retention_days = %v\n", spec.LogRetentionDays)) + buffer.WriteString(fmt.Sprintf("cluster_log_retention_in_days = %v\n", spec.LogRetentionDays)) } buffer.WriteString(fmt.Sprintf("network = \"%v\"\n", spec.Network)) buffer.WriteString(fmt.Sprintf("subnetworks = [\"%v\"]\n", strings.Join(spec.SubNetworks, "\",\""))) @@ -272,14 +268,6 @@ func (e EKS) createVarFile() (err error) { } buffer.WriteString("]\n") } - // For this version we will check for this field value to be present, otherwise we could trigger an unwanted rollout of the node pools for existing clusters. - // The switch from launch configurations to launch templates for the Node Pools requires some manual intervention. - // We could automate this away though. - if spec.NodePoolsLaunchKind == "" { - log.Fatalf(".spec.nodePoolsKind is not set in the cluster configuration file. Please set it explicitly to `launch_configurations`, `launch_templates` or `both` to avoid unwanted node pools rollouts. For new clusters you can use `launch_templates`, for existing clusters please check the Fury EKS Installer docs: https://github.com/sighupio/fury-eks-installer/blob/master/docs/upgrades/v1.9-to-v1.10.0.md") - } else { - buffer.WriteString(fmt.Sprintf("node_pools_launch_kind = \"%v\"\n", spec.NodePoolsLaunchKind)) - } err = ioutil.WriteFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0600) if err != nil { return err @@ -335,7 +323,7 @@ func (e EKS) Prepare() (err error) { // Plan runs a dry run execution func (e EKS) Plan() (err error) { - log.Info("[DRYRUN] Updating EKS Cluster project") + logrus.Info("[DRYRUN] Updating EKS Cluster project") err = e.createVarFile() if err != nil { return err @@ -346,22 +334,22 @@ func (e EKS) Plan() (err error) { tfexec.VarFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("[DRYRUN] Got error while updating EKS: %v", err) + logrus.Fatalf("[DRYRUN] Something went wrong while updating eks. %v", err) return err } if changes { - log.Warn("[DRYRUN] Something has changed in the mean time. Remove --dry-run flag to apply the desired state") + logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") } else { - log.Info("[DRYRUN] Everything is up to date") + logrus.Info("[DRYRUN] Everything is up to date") } - log.Info("[DRYRUN] EKS Updated") + logrus.Info("[DRYRUN] EKS Updated") return nil } // Update runs terraform apply in the project func (e EKS) Update() (string, error) { - log.Info("Updating EKS project") + logrus.Info("Updating EKS project") err := e.createVarFile() if err != nil { return "", err @@ -371,17 +359,17 @@ func (e EKS) Update() (string, error) { tfexec.VarFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("Got error while updating EKS: %v", err) + logrus.Fatalf("Something went wrong while updating eks. %v", err) return "", err } - log.Info("EKS Updated") + logrus.Info("EKS Updated") return e.kubeconfig() } // Destroy runs terraform destroy in the project func (e EKS) Destroy() (err error) { - log.Info("Destroying EKS project") + logrus.Info("Destroying EKS project") err = e.createVarFile() if err != nil { return err @@ -391,25 +379,25 @@ func (e EKS) Destroy() (err error) { tfexec.VarFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("Got error while destroying EKS cluster project: %v", err) + logrus.Fatalf("Something went wrong while destroying EKS cluster project. %v", err) return err } - log.Info("EKS destroyed") + logrus.Info("EKS destroyed") return nil } func (e EKS) kubeconfig() (string, error) { - log.Info("Gathering output file as json") + logrus.Info("Gathering output file as json") var output map[string]tfexec.OutputMeta output, err := e.terraform.Output(context.Background()) if err != nil { - log.Fatalf("Error while getting project output: %v", err) + logrus.Fatalf("Error while getting project output: %v", err) return "", err } var creds string err = json.Unmarshal(output["kubeconfig"].Value, &creds) if err != nil { - log.Fatalf("Error while tranforming the kubeconfig value into string: %v", err) + logrus.Fatalf("Error while tranforming the kubeconfig value into string: %v", err) return "", err } return creds, err diff --git a/internal/cluster/provisioners/gke/provisioner.go b/internal/cluster/provisioners/gke/provisioner.go index a60b3d87d..60f3d52eb 100644 --- a/internal/cluster/provisioners/gke/provisioner.go +++ b/internal/cluster/provisioners/gke/provisioner.go @@ -14,7 +14,7 @@ import ( "github.com/gobuffalo/packr/v2" "github.com/hashicorp/terraform-exec/tfexec" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" cfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sighupio/furyctl/internal/configuration" @@ -43,16 +43,16 @@ func (e *GKE) UpdateMessage() string { var output map[string]tfexec.OutputMeta output, err := e.terraform.Output(context.Background()) if err != nil { - log.Error("Can not get output values") + logrus.Error("Can not get output values") } var clusterEndpoint, clusterOperatorName string err = json.Unmarshal(output["cluster_endpoint"].Value, &clusterEndpoint) if err != nil { - log.Error("Can not get `cluster_endpoint` value") + logrus.Error("Can not get `cluster_endpoint` value") } err = json.Unmarshal(output["operator_ssh_user"].Value, &clusterOperatorName) if err != nil { - log.Error("Can not get `operator_ssh_user` value") + logrus.Error("Can not get `operator_ssh_user` value") } return fmt.Sprintf( `[GKE] Fury @@ -271,7 +271,7 @@ func (e GKE) TerraformFiles() []string { // Plan runs a dry run execution func (e GKE) Plan() (err error) { - log.Info("[DRYRUN] Updating GKE Cluster project") + logrus.Info("[DRYRUN] Updating GKE Cluster project") err = e.createVarFile() if err != nil { return err @@ -282,16 +282,16 @@ func (e GKE) Plan() (err error) { tfexec.VarFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("[DRYRUN] Something went wrong while updating gke. %v", err) + logrus.Fatalf("[DRYRUN] Something went wrong while updating gke. %v", err) return err } if changes { - log.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") + logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") } else { - log.Info("[DRYRUN] Everything is up to date") + logrus.Info("[DRYRUN] Everything is up to date") } - log.Info("[DRYRUN] GKE Updated") + logrus.Info("[DRYRUN] GKE Updated") return nil } @@ -301,7 +301,7 @@ func (e GKE) Prepare() (err error) { // Update runs terraform apply in the project func (e GKE) Update() (string, error) { - log.Info("Updating GKE project") + logrus.Info("Updating GKE project") err := e.createVarFile() if err != nil { return "", err @@ -311,17 +311,17 @@ func (e GKE) Update() (string, error) { tfexec.VarFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("Something went wrong while updating gke. %v", err) + logrus.Fatalf("Something went wrong while updating gke. %v", err) return "", err } - log.Info("GKE Updated") + logrus.Info("GKE Updated") return e.kubeconfig() } // Destroy runs terraform destroy in the project func (e GKE) Destroy() (err error) { - log.Info("Destroying GKE project") + logrus.Info("Destroying GKE project") err = e.createVarFile() if err != nil { return err @@ -331,25 +331,25 @@ func (e GKE) Destroy() (err error) { tfexec.VarFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("Something went wrong while destroying GKE cluster project. %v", err) + logrus.Fatalf("Something went wrong while destroying GKE cluster project. %v", err) return err } - log.Info("GKE destroyed") + logrus.Info("GKE destroyed") return nil } func (e GKE) kubeconfig() (string, error) { - log.Info("Gathering output file as json") + logrus.Info("Gathering output file as json") var output map[string]tfexec.OutputMeta output, err := e.terraform.Output(context.Background()) if err != nil { - log.Fatalf("Error while getting project output: %v", err) + logrus.Fatalf("Error while getting project output: %v", err) return "", err } var creds string err = json.Unmarshal(output["kubeconfig"].Value, &creds) if err != nil { - log.Fatalf("Error while tranforming the kubeconfig value into string: %v", err) + logrus.Fatalf("Error while tranforming the kubeconfig value into string: %v", err) return "", err } return creds, nil diff --git a/internal/cluster/provisioners/vsphere/provisioner.go b/internal/cluster/provisioners/vsphere/provisioner.go index ec998e99c..1afd5842a 100644 --- a/internal/cluster/provisioners/vsphere/provisioner.go +++ b/internal/cluster/provisioners/vsphere/provisioner.go @@ -22,7 +22,8 @@ import ( "github.com/relex/aini" cfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sighupio/furyctl/internal/configuration" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" + "google.golang.org/appengine/log" ) // VSphere represents the VSphere provisioner @@ -60,12 +61,12 @@ func (e *VSphere) UpdateMessage() string { var output map[string]tfexec.OutputMeta output, err := e.terraform.Output(context.Background()) if err != nil { - log.Error("Can not get output values") + logrus.Error("Can not get output values") } var inventoryOutput string err = json.Unmarshal(output["ansible_inventory"].Value, &inventoryOutput) if err != nil { - log.Error("Can not get `ansible_inventory` value") + logrus.Error("Can not get `ansible_inventory` value") } inventory, _ := aini.Parse(strings.NewReader(inventoryOutput)) kubernetes_control_plane_address := strings.Replace( @@ -336,11 +337,11 @@ func downloadAnsibleRoles(workingDirectory string) error { defer os.Setenv("NETRC", p_netrc) netrcpath := filepath.Join(workingDirectory, "configuration/.netrc") - log.Infof("Configuring the NETRC environment variable: %v", netrcpath) + logrus.Infof("Configuring the NETRC environment variable: %v", netrcpath) os.Setenv("NETRC", netrcpath) downloadPath := filepath.Join(workingDirectory, "provision/roles") - log.Infof("Ansible roles download path: %v", downloadPath) + logrus.Infof("Ansible roles download path: %v", downloadPath) if err := os.Mkdir(downloadPath, 0755); err != nil { return err } @@ -357,7 +358,7 @@ func downloadAnsibleRoles(workingDirectory string) error { // Plan runs a dry run execution func (e VSphere) Plan() (err error) { - log.Info("[DRYRUN] Updating VSphere Cluster project") + logrus.Info("[DRYRUN] Updating VSphere Cluster project") // TODO: give the name of the file err = e.createVarFile() if err != nil { @@ -369,22 +370,22 @@ func (e VSphere) Plan() (err error) { tfexec.VarFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("[DRYRUN] Something went wrong while updating vsphere. %v", err) + logrus.Fatalf("[DRYRUN] Something went wrong while updating vsphere. %v", err) return err } if changes { - log.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") + logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") } else { - log.Info("[DRYRUN] Everything is up to date") + logrus.Info("[DRYRUN] Everything is up to date") } - log.Info("[DRYRUN] VSphere Updated") + logrus.Info("[DRYRUN] VSphere Updated") return nil } // Update runs terraform apply in the project func (e VSphere) Update() (string, error) { - log.Info("Updating VSphere project") + logrus.Info("Updating VSphere project") err := e.createVarFile() if err != nil { return "", err @@ -394,26 +395,26 @@ func (e VSphere) Update() (string, error) { tfexec.VarFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("Something went wrong while updating vsphere. %v", err) + logrus.Fatalf("Something went wrong while updating vsphere. %v", err) return "", err } var output map[string]tfexec.OutputMeta output, err = e.terraform.Output(context.Background()) if err != nil { - log.Error("Can not get output values") + logrus.Error("Can not get output values") return "", err } var ansibleInventory, haproxyConfig string err = json.Unmarshal(output["ansible_inventory"].Value, &ansibleInventory) if err != nil { - log.Error("Can not get `ansible_inventory` value") + logrus.Error("Can not get `ansible_inventory` value") return "", err } err = json.Unmarshal(output["haproxy_config"].Value, &haproxyConfig) if err != nil { - log.Error("Can not get `haproxy_config` value") + logrus.Error("Can not get `haproxy_config` value") return "", err } @@ -439,7 +440,7 @@ func (e VSphere) Update() (string, error) { // Destroy runs terraform destroy in the project func (e VSphere) Destroy() (err error) { - log.Info("Destroying VSphere project") + logrus.Info("Destroying VSphere project") err = e.createVarFile() if err != nil { return err @@ -449,10 +450,10 @@ func (e VSphere) Destroy() (err error) { tfexec.VarFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), ) if err != nil { - log.Fatalf("Something went wrong while destroying VSphere cluster project. %v", err) + logrus.Fatalf("Something went wrong while destroying VSphere cluster project. %v", err) return err } - log.Info("VSphere destroyed") + logrus.Info("VSphere destroyed") return nil } @@ -488,7 +489,7 @@ func createPKI(workingDirectory string) error { filepath.Join(downloadPath, downloadedExecutableName), filepath.Join(downloadPath, wantedExecutableName), ); err != nil { - log.Fatal(err) + logrus.Fatal(err) } os.Chmod(filepath.Join(downloadPath, wantedExecutableName), 0755) @@ -497,30 +498,30 @@ func createPKI(workingDirectory string) error { cmd.Dir = downloadPath out, err := cmd.Output() if err != nil { - log.Debugf("%s", out) - log.Fatal(err) + logrus.Debugf("%s", out) + logrus.Fatal(err) } cmd = exec.Command("./furyagent", "init", "etcd") cmd.Dir = downloadPath out, err = cmd.Output() if err != nil { - log.Debugf("%s", out) - log.Fatal(err) + logrus.Debugf("%s", out) + logrus.Fatal(err) } return nil } func runAnsiblePlaybook(workingDir string, logDir string) (string, error) { - log.Infof("Run Ansible playbook in : %v", workingDir) + logrus.Infof("Run Ansible playbook in : %v", workingDir) // TODO: Get the debug flag from the CLI to output both to a file and stdout // open the log file for writing logFilePath := filepath.Join(logDir, "ansible.log") logFile, err := os.Create(logFilePath) if err != nil { - log.Errorf("Can not open the log file %v", logFilePath) + logrus.Errorf("Can not open the log file %v", logFilePath) return "", err } defer logFile.Close() @@ -531,8 +532,8 @@ func runAnsiblePlaybook(workingDir string, logDir string) (string, error) { cmd.Stderr = logFile err = cmd.Run() if err != nil { - log.Debug("Please make sure you have Ansible installed in this machine") - log.Fatal(err) + logrus.Debug("Please make sure you have Ansible installed in this machine") + logrus.Fatal(err) return "", err } @@ -542,12 +543,12 @@ func runAnsiblePlaybook(workingDir string, logDir string) (string, error) { cmd.Stderr = logFile err = cmd.Start() if err != nil { - log.Fatal(err) + logrus.Fatal(err) return "", err } err = cmd.Wait() if err != nil { - log.Fatal(err) + logrus.Fatal(err) return "", err } diff --git a/internal/configuration/config.go b/internal/configuration/config.go index 01970e5aa..eb8294a07 100644 --- a/internal/configuration/config.go +++ b/internal/configuration/config.go @@ -11,8 +11,8 @@ import ( bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" + "github.com/sirupsen/logrus" - log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) @@ -47,13 +47,13 @@ type Metadata struct { func Parse(path string) (*Configuration, error) { content, err := ioutil.ReadFile(path) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return nil, err } baseConfig := &Configuration{} err = yaml.Unmarshal(content, &baseConfig) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return nil, err } @@ -71,17 +71,17 @@ func Parse(path string) (*Configuration, error) { } return baseConfig, nil default: - log.Errorf("Error parsing the configuration file. Parser not found for %v kind", baseConfig.Kind) + logrus.Errorf("Error parsing the configuration file. Parser not found for %v kind", baseConfig.Kind) return nil, fmt.Errorf("parser not found for %v kind", baseConfig.Kind) } } func clusterParser(config *Configuration) (err error) { provisioner := config.Provisioner - log.Debugf("provisioner: %v", provisioner) + logrus.Debugf("provisioner: %v", provisioner) specBytes, err := yaml.Marshal(config.Spec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } switch { @@ -89,7 +89,7 @@ func clusterParser(config *Configuration) (err error) { eksSpec := clustercfg.EKS{} err = yaml.Unmarshal(specBytes, &eksSpec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } config.Spec = eksSpec @@ -104,7 +104,7 @@ func clusterParser(config *Configuration) (err error) { } err = yaml.Unmarshal(specBytes, &gkeSpec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } config.Spec = gkeSpec @@ -118,23 +118,23 @@ func clusterParser(config *Configuration) (err error) { } err = yaml.Unmarshal(specBytes, &vsphereSpec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } config.Spec = vsphereSpec return nil default: - log.Error("Error parsing the configuration file. Provisioner not found") + logrus.Error("Error parsing the configuration file. Provisioner not found") return errors.New("cluster provisioner not found") } } func bootstrapParser(config *Configuration) (err error) { provisioner := config.Provisioner - log.Debugf("provisioner: %v", provisioner) + logrus.Debugf("provisioner: %v", provisioner) specBytes, err := yaml.Marshal(config.Spec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } switch { @@ -146,7 +146,7 @@ func bootstrapParser(config *Configuration) (err error) { } err = yaml.Unmarshal(specBytes, &awsSpec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } config.Spec = awsSpec @@ -159,13 +159,13 @@ func bootstrapParser(config *Configuration) (err error) { } err = yaml.Unmarshal(specBytes, &gcpSpec) if err != nil { - log.Errorf("error parsing configuration file: %v", err) + logrus.Errorf("error parsing configuration file: %v", err) return err } config.Spec = gcpSpec return nil default: - log.Error("Error parsing the configuration file. Provisioner not found") + logrus.Error("Error parsing the configuration file. Provisioner not found") return errors.New("bootstrap provisioner not found") } } diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go index 07582ce28..e15a01033 100644 --- a/internal/configuration/templates.go +++ b/internal/configuration/templates.go @@ -10,8 +10,8 @@ import ( bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" + "github.com/sirupsen/logrus" - log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) @@ -32,7 +32,7 @@ func Template(kind string, provisioner string) (string, error) { return "", err } default: - log.Errorf("Error creating a template configuration file. Parser not found for %v kind", kind) + logrus.Errorf("Error creating a template configuration file. Parser not found for %v kind", kind) return "", fmt.Errorf("error creating a template configuration file. Parser not found for %v kind", kind) } b, err := yaml.Marshal(config) diff --git a/internal/project/project.go b/internal/project/project.go index 72f663713..53f3a7083 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -11,7 +11,7 @@ import ( "os" "github.com/sighupio/furyctl/pkg/utils" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) const ( @@ -30,10 +30,10 @@ type Project struct { func (p *Project) Reset() (err error) { _, err = os.Stat(p.Path) if !os.IsNotExist(err) { // Exists - log.Warnf("Removing %v directory", p.Path) + logrus.Warnf("Removing %v directory", p.Path) err = os.RemoveAll(p.Path) if err != nil { - log.Errorf("Error removing the base dir %v. %v", p.Path, err) + logrus.Errorf("Error removing the base dir %v. %v", p.Path, err) return err } } @@ -44,14 +44,14 @@ func (p *Project) Reset() (err error) { func (p *Project) CreateSubDirs(subDirs []string) (err error) { _, err = os.Stat(p.Path) if !os.IsNotExist(err) { - log.Error(pathAlreadyExistsErr) + logrus.Error(pathAlreadyExistsErr) return errors.New(pathAlreadyExistsErr) } if os.IsNotExist(err) { for _, subDir := range subDirs { err = os.MkdirAll(fmt.Sprintf("%v/%v", p.Path, subDir), defaultDirPermission) if err != nil { - log.Errorf(pathCreationErr, err) + logrus.Errorf(pathCreationErr, err) return err } } @@ -74,7 +74,7 @@ func (p *Project) WriteFile(fileName string, content []byte) (err error) { func (p *Project) Check() error { _, err := os.Stat(p.Path) if os.IsNotExist(err) { - log.Errorf("Directory does not exists. %v", err) + logrus.Errorf("Directory does not exists. %v", err) return errors.New("Directory does not exists") } return nil diff --git a/internal/provisioners/provisioners.go b/internal/provisioners/provisioners.go index 2772efd45..776e61c25 100644 --- a/internal/provisioners/provisioners.go +++ b/internal/provisioners/provisioners.go @@ -17,7 +17,7 @@ import ( "github.com/sighupio/furyctl/internal/cluster/provisioners/gke" "github.com/sighupio/furyctl/internal/cluster/provisioners/vsphere" "github.com/sighupio/furyctl/internal/configuration" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) // Provisioner represents a kubernetes terraform provisioner @@ -48,7 +48,7 @@ func Get(config configuration.Configuration) (Provisioner, error) { case config.Kind == "Bootstrap": return getBootstrapProvisioner(config) default: - log.Errorf("Kind %v not found", config.Kind) + logrus.Errorf("Kind %v not found", config.Kind) return nil, fmt.Errorf("kind %v not found", config.Kind) } } @@ -62,7 +62,7 @@ func getClusterProvisioner(config configuration.Configuration) (Provisioner, err case config.Provisioner == "vsphere": return vsphere.New(&config), nil default: - log.Error("Provisioner not found") + logrus.Error("Provisioner not found") return nil, errors.New("Provisioner not found") } } @@ -73,7 +73,7 @@ func getBootstrapProvisioner(config configuration.Configuration) (Provisioner, e case config.Provisioner == "gcp": return gcp.New(&config), nil default: - log.Error("Provisioner not found") + logrus.Error("Provisioner not found") return nil, errors.New("Provisioner not found") } } diff --git a/internal/schema/santhosh/errors.go b/internal/schema/santhosh/errors.go deleted file mode 100644 index 0d65957c5..000000000 --- a/internal/schema/santhosh/errors.go +++ /dev/null @@ -1,110 +0,0 @@ -package santhosh - -import ( - "errors" - "strconv" - "strings" - - "github.com/santhosh-tekuri/jsonschema" - "golang.org/x/exp/slices" -) - -var ErrObjTypeAssertion = errors.New("obj type assertion failed") - -func JoinPtrPath(path []any) string { - strpath := "#" - - for _, key := range path { - switch v := key.(type) { - case int: - strpath += "/" + strconv.Itoa(v) - - case string: - strpath += "/" + v - } - } - - return strpath -} - -func GetValueAtPath(obj any, path []any) (any, error) { - for _, key := range path { - switch v := key.(type) { - case int: - tobj, ok := obj.([]any) - if !ok { - return nil, ErrObjTypeAssertion - } - - obj = tobj[v] - - case string: - tobj, ok := obj.(map[string]any) - if !ok { - return nil, ErrObjTypeAssertion - } - - obj = tobj[v] - } - } - - return obj, nil -} - -func GetPtrPaths(err error) [][]any { - var terr *jsonschema.ValidationError - - if errors.As(err, &terr) { - ptrs := extractPtrs(terr) - - mptrs := minimizePtrs(ptrs) - - return explodePtrs(mptrs) - } - - return nil -} - -func extractPtrs(err *jsonschema.ValidationError) []string { - ptrs := []string{err.InstancePtr} - - for _, cause := range err.Causes { - if len(cause.Causes) > 0 { - ptrs = append(ptrs, extractPtrs(cause)...) - } else { - ptrs = append(ptrs, cause.InstancePtr) - } - } - - return ptrs -} - -func minimizePtrs(ptrs []string) []string { - slices.Sort(ptrs) - - return slices.Compact(ptrs) -} - -func explodePtrs(ptrs []string) [][]any { - eptrs := make([][]any, len(ptrs)) - for i, p := range ptrs { - eptrs[i] = explodePtr(strings.TrimLeft(p, "#/")) - } - - return eptrs -} - -func explodePtr(ptr string) []any { - parts := strings.Split(ptr, "/") - ptrParts := make([]any, len(parts)) - - for i, part := range parts { - if numpart, err := strconv.Atoi(part); err == nil { - ptrParts[i] = numpart - } else { - ptrParts[i] = part - } - } - - return ptrParts -} diff --git a/internal/schema/santhosh/loader.go b/internal/schema/santhosh/loader.go index f7ce4f54e..b4e9cec9d 100644 --- a/internal/schema/santhosh/loader.go +++ b/internal/schema/santhosh/loader.go @@ -2,26 +2,33 @@ package santhosh import ( "bytes" + "errors" "fmt" "os" "github.com/santhosh-tekuri/jsonschema" ) +var ( + ErrCannotLoadSchema = errors.New("failed to read schema file") +) + func LoadSchema(schemaPath string) (schema *jsonschema.Schema, errSchema error) { + berr := fmt.Errorf("%w: '%s'", ErrCannotLoadSchema, schemaPath) + data, err := os.ReadFile(schemaPath) if err != nil { - return nil, fmt.Errorf("failed to read schema file: %w", err) + return nil, fmt.Errorf("%w: %v", berr, err) } compiler := jsonschema.NewCompiler() if err = compiler.AddResource(schemaPath, bytes.NewReader(data)); err != nil { - return nil, fmt.Errorf("failed to add resource to json schema compiler: %w", err) + return nil, fmt.Errorf("%w: %v", berr, err) } schema, errSchema = compiler.Compile(schemaPath) if errSchema != nil { - return nil, fmt.Errorf("failed to compile json schema: %w", err) + return nil, fmt.Errorf("%w: %v", berr, err) } return schema, nil diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index c478e604b..d43a548a5 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -12,7 +12,7 @@ import ( "github.com/denisbrodbeck/machineid" "github.com/dukex/mixpanel" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) const ( @@ -86,10 +86,10 @@ func track(event string, success bool, token string, props map[string]interface{ e := &mixpanel.Event{Properties: props} trackID := getTrackID(token) if err := mixpanelClient.Track(trackID, event, e); err != nil { - log.WithError(err).Debugf("Failed to send analytics: %s", err) + logrus.WithError(err).Debugf("Failed to send analytics: %s", err) } } else { - log.Debugf("not sending event for %s", event) + logrus.Debugf("not sending event for %s", event) } } @@ -103,7 +103,7 @@ func getTrackID(token string) string { func generateMachineID() string { mid, err := machineid.ProtectedID("furyctl") if err != nil { - log.WithError(err).Debug("failed to generate a machine id") + logrus.WithError(err).Debug("failed to generate a machine id") mid = "na" } diff --git a/pkg/terraform/install.go b/pkg/terraform/install.go index dd66d7167..a221d9c1e 100644 --- a/pkg/terraform/install.go +++ b/pkg/terraform/install.go @@ -16,16 +16,16 @@ import ( "github.com/hashicorp/terraform-exec/tfexec" "github.com/hashicorp/terraform-exec/tfinstall" "github.com/sighupio/furyctl/pkg/utils" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) // ensure ensures a working terraform version to be used in the project func ensure(terraformVersion string, terraformDownloadPath string) (binPath string, err error) { if terraformVersion != "" { - log.Debugf("Installing terraform %v version", terraformVersion) + logrus.Debugf("Installing terraform %v version", terraformVersion) return install(terraformVersion, terraformDownloadPath) } - log.Debug("Installing terraform latest version") + logrus.Debug("Installing terraform latest version") return installLatest(terraformDownloadPath) } @@ -33,7 +33,7 @@ func alreadyAvailable(terraformVersion string, terraformDownloadPath string) (bo // validate version v, err := version.NewVersion(terraformVersion) if err != nil { - log.Warning(err) + logrus.Warning(err) return false, "" } expectedTerraformBinary := filepath.Join(terraformDownloadPath, "terraform") @@ -41,14 +41,14 @@ func alreadyAvailable(terraformVersion string, terraformDownloadPath string) (bo if err != nil { defer os.RemoveAll(expectedTerraformBinary) defer os.RemoveAll(binPath) - log.Warning(err) + logrus.Warning(err) return false, "" } wd, err := ioutil.TempDir("", "tfexec") if err != nil { defer os.RemoveAll(expectedTerraformBinary) defer os.RemoveAll(binPath) - log.Warning(err) + logrus.Warning(err) return false, "" } defer os.RemoveAll(wd) // Clean up @@ -56,25 +56,25 @@ func alreadyAvailable(terraformVersion string, terraformDownloadPath string) (bo if err != nil { defer os.RemoveAll(expectedTerraformBinary) defer os.RemoveAll(binPath) - log.Warning(err) + logrus.Warning(err) return false, "" } installedV, _, err := tf.Version(context.Background(), true) if err != nil { defer os.RemoveAll(expectedTerraformBinary) defer os.RemoveAll(binPath) - log.Warning(err) + logrus.Warning(err) return false, "" } if !v.Equal(installedV) { - log.Warning("The installed version is different to the required version") - log.Debug("Removing old terraform version") + logrus.Warning("The installed version is different to the required version") + logrus.Debug("Removing old terraform version") defer os.RemoveAll(expectedTerraformBinary) defer os.RemoveAll(binPath) return false, "" } - log.Debugf("%s is up to date with the requested %s version", binPath, terraformVersion) - log.Info("terraform is up to date") + logrus.Debugf("%s is up to date with the requested %s version", binPath, terraformVersion) + logrus.Info("terraform is up to date") return true, binPath } @@ -87,7 +87,7 @@ func install(terraformVersion string, terraformDownloadPath string) (binPath str } binPath, err = tfinstall.Find(context.Background(), tfinstall.ExactVersion(terraformVersion, terraformDownloadPath)) if err != nil { - log.Errorf("Error downloading version %v: %v", terraformVersion, err) + logrus.Errorf("Error downloading version %v: %v", terraformVersion, err) return "", err } } @@ -107,7 +107,7 @@ func installLatest(terraformDownloadPath string) (binPath string, err error) { } binPath, err = tfinstall.Find(context.Background(), tfinstall.LatestVersion(terraformDownloadPath, false)) if err != nil { - log.Errorf("Error downloading latest version: %v", err) + logrus.Errorf("Error downloading latest version: %v", err) return "", err } } diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index b9691523c..37126bc07 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/hashicorp/terraform-exec/tfexec" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) type Options struct { @@ -99,7 +99,7 @@ func envMap(environ []string) map[string]string { if !strings.HasPrefix(k, varEnvVarPrefix) && !forbidenTerraformEnvs[k] { env[k] = v } else { - log.Warnf("%v Environment variable discarted. Executor will not use it", k) + logrus.Warnf("%v Environment variable discarted. Executor will not use it", k) } } return env @@ -107,13 +107,13 @@ func envMap(environ []string) map[string]string { func configureLogger(tf *tfexec.Terraform, workingDir string, logDir string, debug bool) (err error) { logFile, err := os.Create(fmt.Sprintf("%v/%v/terraform.logs", workingDir, logDir)) - tf.SetLogger(log.StandardLogger()) + tf.SetLogger(logrus.StandardLogger()) c := &tfwriter{ logfile: logFile, debug: debug, } if err != nil { - log.Errorf("Can not init log file. %v", err) + logrus.Errorf("Can not init log file. %v", err) return err } tf.SetStdout(c) From cbda09d9f38d780599f61eae4e13e44e27072553 Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 14 Sep 2022 19:33:38 +0200 Subject: [PATCH 008/383] feat: refactor validate command and add versions validation - refactor yaml package to handle v2 and v3 - improve error handling in root command - refactor "validate" command to "validate config" command - handle version compantibility in "validate config" command - refactor common "validate config" logic to make it available for the "validate dependencies" command --- cmd/cmdutil/cmdutil.go | 40 ---- cmd/root.go | 3 +- cmd/template.go | 11 +- cmd/validate.go | 90 +-------- cmd/validate/config.go | 186 ++++++++++++++++++ cmd/validate/util.go | 113 ++--------- cmd/vendor.go | 3 +- .../provisioners/vsphere/provisioner.go | 3 +- internal/distribution/model.go | 78 ++++++++ internal/semver/compare.go | 67 +++++++ internal/template/model.go | 2 +- internal/yaml/yaml.go | 36 +++- internal/yaml/yaml_test.go | 13 +- 13 files changed, 398 insertions(+), 247 deletions(-) delete mode 100644 cmd/cmdutil/cmdutil.go create mode 100644 cmd/validate/config.go create mode 100644 internal/distribution/model.go create mode 100644 internal/semver/compare.go diff --git a/cmd/cmdutil/cmdutil.go b/cmd/cmdutil/cmdutil.go deleted file mode 100644 index 13bb566b2..000000000 --- a/cmd/cmdutil/cmdutil.go +++ /dev/null @@ -1,40 +0,0 @@ -package cmdutil - -import ( - "errors" - "os" - - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" -) - -var ( - ErrNoOutputFormat = errors.New("output cannot be nil") - ErrNoConfigFileFound = errors.New("no config files found") - ErrUnknownOutputFormat = errors.New("unknown output format, supported ones are: json, text") - ErrTooManyArguments = errors.New("too many arguments") -) - -func GetWd() string { - cwd, err := os.Getwd() - if err != nil { - logrus.Fatal(err) - } - - return cwd -} - -func LoadConfig[T any](file string) T { - var conf T - - configData, err := os.ReadFile(file) - if err != nil { - logrus.Fatal(err) - } - - if err := yaml.Unmarshal(configData, &conf); err != nil { - logrus.Fatal(err) - } - - return conf -} diff --git a/cmd/root.go b/cmd/root.go index 71cee84ec..b70d028f5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -92,7 +92,8 @@ Furyctl is a simple CLI tool to: - download and manage the Kubernetes Fury Distribution (KFD) modules - create and manage Kubernetes Fury clusters `, - SilenceUsage: true, + SilenceUsage: true, + SilenceErrors: true, PersistentPreRun: func(cmd *cobra.Command, _ []string) { bootstrapLogrus(cmd) }, diff --git a/cmd/template.go b/cmd/template.go index 06e4b6713..a87d33cf3 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -10,10 +10,9 @@ import ( "path/filepath" "github.com/sighupio/furyctl/internal/merge" - yaml2 "github.com/sighupio/furyctl/internal/yaml" + "github.com/sighupio/furyctl/internal/yaml" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "gopkg.in/yaml.v2" "github.com/sighupio/furyctl/internal/template" ) @@ -29,7 +28,7 @@ var ( The generated folder will be created starting from a provided template and the parameters set in a configuration file that is merged with default values.`, SilenceUsage: true, SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { //TODO(rm-2470): To be reworked in redmine task - Define template command flags. source := "source" target := "target" @@ -37,12 +36,12 @@ The generated folder will be created starting from a provided template and the p distributionFilePath := "distribution.yaml" furyctlFilePath := "furyctl.yaml" - distributionFile, err := yaml2.FromFile[map[any]any](distributionFilePath) + distributionFile, err := yaml.FromFileV2[map[any]any](distributionFilePath) if err != nil { return fmt.Errorf("%s - %w", distributionFilePath, err) } - furyctlFile, err := yaml2.FromFile[map[any]any](furyctlFilePath) + furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) if err != nil { return fmt.Errorf("%s - %w", furyctlFilePath, err) } @@ -61,7 +60,7 @@ The generated folder will be created starting from a provided template and the p return err } - outYaml, err := yaml.Marshal(mergedDistribution) + outYaml, err := yaml.MarshalV2(mergedDistribution) if err != nil { return err } diff --git a/cmd/validate.go b/cmd/validate.go index a7cca6c37..86a1fd9b7 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -1,104 +1,18 @@ package cmd import ( - "fmt" - "path/filepath" - "github.com/spf13/cobra" - "github.com/sighupio/furyctl/cmd/cmdutil" "github.com/sighupio/furyctl/cmd/validate" - "github.com/sighupio/furyctl/internal/schema/santhosh" ) var ( - errDefaultsDownload = fmt.Errorf("error downloading json schema for furyctl.yaml") - validateCmd = &cobra.Command{ Use: "validate", - Short: "Validate Furyfile", - RunE: func(cmd *cobra.Command, _ []string) error { - debug := cmd.Flag("debug").Value.String() == "true" - furyctlFilePath := cmd.Flag("config").Value.String() - schemasLocation := cmd.Flag("schemas-location").Value.String() - defaultsLocation := cmd.Flag("defaults-location").Value.String() - - schemasPath, err := validate.DownloadFolder(schemasLocation, "schemas") - if err != nil { - return err - } - if !debug { - defer validate.CleanupTempDir(filepath.Base(schemasPath)) - } - - defaultsPath, err := validate.DownloadFolder(defaultsLocation, "defaults") - if err != nil { - return err - } - if !debug { - defer validate.CleanupTempDir(filepath.Base(defaultsPath)) - } - - hasErrors := error(nil) - - minimalConf := cmdutil.LoadConfig[validate.FuryctlConfig](furyctlFilePath) - - schemaPath := validate.GetSchemaPath(schemasPath, minimalConf) - defaultPath := validate.GetDefaultPath(defaultsPath, minimalConf) - - defaultedFuryctlFilePath, err := validate.MergeConfigAndDefaults(furyctlFilePath, defaultPath) - if err != nil { - return err - } - if !debug { - defer validate.CleanupTempDir(filepath.Base(defaultedFuryctlFilePath)) - } - - schema, err := santhosh.LoadSchema(schemaPath) - if err != nil { - return err - } - - conf := cmdutil.LoadConfig[any](defaultedFuryctlFilePath) - - if err := schema.ValidateInterface(conf); err != nil { - validate.PrintResults(err, defaultedFuryctlFilePath) - - hasErrors = validate.ErrHasValidationErrors - } - - validate.PrintSummary(hasErrors != nil) - - return hasErrors - }, + Short: "Validate fury config files and dependencies", } ) func init() { - validateCmd.Flags().StringP( - "config", - "c", - "furyctl.yaml", - "Path to the furyctl.yaml file", - ) - - validateCmd.Flags().StringP( - "schemas-location", - "", - "", - "Base URL used to download schemas. "+ - "It can either be a local path(eg: /path/to/fury/distribution//schemas) or "+ - "a remote URL(eg: https://git@github.com/sighupio/fury-distribution//schemas?ref=BRANCH_NAME)."+ - "Any format supported by hashicorp/go-getter can be used.", - ) - - validateCmd.Flags().StringP( - "defaults-location", - "", - "", - "Base URL used to download defaults. "+ - "It can either be a local path(eg: /path/to/fury/distribution//defaults) or "+ - "a remote URL(eg: https://git@github.com/sighupio/fury-distribution//defaults?ref=BRANCH_NAME)."+ - "Any format supported by hashicorp/go-getter can be used.", - ) + validateCmd.AddCommand(validate.NewConfigCmd(version)) } diff --git a/cmd/validate/config.go b/cmd/validate/config.go new file mode 100644 index 000000000..a3695b746 --- /dev/null +++ b/cmd/validate/config.go @@ -0,0 +1,186 @@ +package validate + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/schema/santhosh" + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/yaml" +) + +const defaultBaseUrl = "https://git@github.com/sighupio/fury-distribution?ref=%s" + +var errHasValidationErrors = fmt.Errorf("furyctl.yaml contains validation errors") + +func NewConfigCmd(version string) *cobra.Command { + cmd := &cobra.Command{ + Use: "config", + Short: "Validate furyctl.yaml file", + RunE: func(cmd *cobra.Command, _ []string) error { + debug := cmd.Flag("debug").Value.String() == "true" + furyctlPath := cmd.Flag("config").Value.String() + distroLocation := cmd.Flag("distro-location").Value.String() + + minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) + if err != nil { + return err + } + + furyctlConfVersion := minimalConf.Spec.DistributionVersion + + if version != "dev" { + furyctlBinVersion, err := semver.NewVersion(version) + if err != nil { + return err + } + + sameMinors := semver.SameMinor(furyctlConfVersion, furyctlBinVersion) + + if !sameMinors { + logrus.Warnf( + "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", + furyctlBinVersion, + furyctlConfVersion, + ) + } + } + + if distroLocation == "" { + distroLocation = fmt.Sprintf(defaultBaseUrl, furyctlConfVersion.String()) + } + + repoPath, err := downloadDirectory(distroLocation) + if err != nil { + return err + } + if !debug { + defer cleanupTempDir(filepath.Base(repoPath)) + } + + kfdPath := filepath.Join(repoPath, "kfd.yaml") + kfdManifest, err := yaml.FromFileV3[distribution.Manifest](kfdPath) + if err != nil { + return err + } + + if !semver.SameMinor(furyctlConfVersion, kfdManifest.Version) { + return fmt.Errorf( + "minor versions mismatch: furyctl.yaml has %s, but furyctl has %s", + furyctlConfVersion.String(), + kfdManifest.Version.String(), + ) + } + + schemaPath := getSchemaPath(repoPath, minimalConf) + defaultPath := getDefaultPath(repoPath) + + defaultedFuryctlPath, err := mergeConfigAndDefaults(furyctlPath, defaultPath) + if err != nil { + return err + } + if !debug { + defer cleanupTempDir(filepath.Base(defaultedFuryctlPath)) + } + + schema, err := santhosh.LoadSchema(schemaPath) + if err != nil { + return err + } + + hasErrors := error(nil) + conf, err := yaml.FromFileV3[any](defaultedFuryctlPath) + if err != nil { + return err + } + + if err := schema.ValidateInterface(conf); err != nil { + logrus.Debugf("Config file: %s", defaultedFuryctlPath) + + fmt.Println(err) + + hasErrors = errHasValidationErrors + } + + if hasErrors == nil { + fmt.Println("Validation succeeded") + } + + return hasErrors + }, + } + + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Base URL used to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: https://git@github.com/sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + return cmd +} + +func mergeConfigAndDefaults(furyctlFilePath string, defaultsFilePath string) (string, error) { + defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) + if err != nil { + return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) + } + + furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) + if err != nil { + return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) + } + + defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") + distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") + + distroMerger := merge.NewMerger(defaultsModel, distributionModel) + + defaultedDistribution, err := distroMerger.Merge() + if err != nil { + return "", fmt.Errorf("%w: %v", ErrMergeDistroConfig, err) + } + + furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") + defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") + + furyctlMerger := merge.NewMerger(furyctlModel, defaultedDistributionModel) + + defaultedFuryctl, err := furyctlMerger.Merge() + if err != nil { + return "", fmt.Errorf("%w: %v", ErrMergeCompleteConfig, err) + } + + outYaml, err := yaml.MarshalV2(defaultedFuryctl) + if err != nil { + return "", fmt.Errorf("%w: %v", ErrYamlMarshalFile, err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") + if err != nil { + return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) + } + + confPath := filepath.Join(outDirPath, "config.yaml") + if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return "", fmt.Errorf("%w: %v", ErrWriteFile, err) + } + + return confPath, nil +} diff --git a/cmd/validate/util.go b/cmd/validate/util.go index c4456119a..0f947e809 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -9,14 +9,10 @@ import ( "github.com/hashicorp/go-getter" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" - "github.com/sighupio/furyctl/internal/merge" - yaml2 "github.com/sighupio/furyctl/internal/yaml" + "github.com/sighupio/furyctl/internal/distribution" ) -const defaultBaseUrl = "https://git@github.com/sighupio/fury-distribution//%s?ref=feature/create-draft-of-the-furyctl-yaml-json-schema" - var ( downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} @@ -33,111 +29,37 @@ var ( ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) -type FuryctlConfig struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Spec struct { - DistributionVersion string `yaml:"distributionVersion"` - } `yaml:"spec"` -} - -func GetSchemaPath(basePath string, conf FuryctlConfig) string { +func getSchemaPath(basePath string, conf distribution.FuryctlConfig) string { avp := strings.Split(conf.ApiVersion, "/") ns := strings.Replace(avp[0], ".sighup.io", "", 1) ver := avp[1] filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind), ns, ver) - return filepath.Join(basePath, conf.Spec.DistributionVersion, filename) + return filepath.Join(basePath, "schemas", filename) } -func GetDefaultPath(basePath string, conf FuryctlConfig) string { - return filepath.Join(basePath, conf.Spec.DistributionVersion, "furyctl-defaults.yaml") +func getDefaultPath(basePath string) string { + return filepath.Join(basePath, "furyctl-defaults.yaml") } -func DownloadFolder(distroLocation string, name string) (string, error) { - src := fmt.Sprintf(defaultBaseUrl, name) - if distroLocation != "" { - src = distroLocation - } - - dir, err := os.MkdirTemp("", fmt.Sprintf("furyctl-%s-", name)) +func downloadDirectory(src string) (string, error) { + baseDst, err := os.MkdirTemp("", "furyctl-") if err != nil { return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) } - logrus.Debugf("Downloading '%s' from '%s' in '%s'", name, src, dir) - - if err := clientGet(src, dir); err != nil { - return "", fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) - } - - return dir, nil -} - -func MergeConfigAndDefaults(furyctlFilePath string, defaultsFilePath string) (string, error) { - defaultsFile, err := yaml2.FromFile[map[any]any](defaultsFilePath) - if err != nil { - return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) - } - - furyctlFile, err := yaml2.FromFile[map[any]any](furyctlFilePath) - if err != nil { - return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) - } - - defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") - distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - - distroMerger := merge.NewMerger(defaultsModel, distributionModel) - - defaultedDistribution, err := distroMerger.Merge() - if err != nil { - return "", fmt.Errorf("%w: %v", ErrMergeDistroConfig, err) - } + dst := filepath.Join(baseDst, "data") - furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") + logrus.Debugf("Downloading '%s' in '%s'", src, dst) - furyctlMerger := merge.NewMerger(furyctlModel, defaultedDistributionModel) - - defaultedFuryctl, err := furyctlMerger.Merge() - if err != nil { - return "", fmt.Errorf("%w: %v", ErrMergeCompleteConfig, err) - } - - outYaml, err := yaml.Marshal(defaultedFuryctl) - if err != nil { - return "", fmt.Errorf("%w: %v", ErrYamlMarshalFile, err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") - if err != nil { - return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) - } - - confPath := filepath.Join(outDirPath, "config.yaml") - if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - return "", fmt.Errorf("%w: %v", ErrWriteFile, err) - } - - return confPath, nil -} - -func PrintSummary(hasErrors bool) { - if hasErrors { - fmt.Println("Validation failed") - } else { - fmt.Println("Validation succeeded") + if err := clientGet(src, dst); err != nil { + return "", fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } -} - -func PrintResults(err error, configFile string) { - fmt.Printf("CONFIG FILE %s\n", configFile) - fmt.Println(err) + return dst, nil } -func CleanupTempDir(dir string) { +func cleanupTempDir(dir string) { if err := os.RemoveAll(dir); err != nil { if !errors.Is(err, os.ErrNotExist) { logrus.Error(err) @@ -163,11 +85,12 @@ func clientGet(src, dst string) error { Mode: getter.ClientModeDir, } - if err := client.Get(); err == nil { - logrus.Debug("Download successful") - + err := client.Get() + if err == nil { return nil } + + logrus.Debug(err) } return errDownloadOptionsExausted @@ -176,7 +99,7 @@ func clientGet(src, dst string) error { // urlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. func urlHasForcedProtocol(url string) bool { for _, dp := range downloadProtocols { - if strings.HasPrefix(url, dp) { + if dp != "" && strings.HasPrefix(url, dp) { return true } } diff --git a/cmd/vendor.go b/cmd/vendor.go index ed8f5b725..d5c1d2f81 100644 --- a/cmd/vendor.go +++ b/cmd/vendor.go @@ -34,7 +34,7 @@ var vendorCmd = &cobra.Command{ Long: "download KFD modules and dependencies specified in Furyfile.yml", SilenceUsage: true, SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { viper.SetConfigType("yml") viper.AddConfigPath(".") viper.SetConfigName(configFile) @@ -53,7 +53,6 @@ var vendorCmd = &cobra.Command{ } list, err := config.Parse(conf.Prefix) - if err != nil { return err } diff --git a/internal/cluster/provisioners/vsphere/provisioner.go b/internal/cluster/provisioners/vsphere/provisioner.go index 1afd5842a..9581758fb 100644 --- a/internal/cluster/provisioners/vsphere/provisioner.go +++ b/internal/cluster/provisioners/vsphere/provisioner.go @@ -23,7 +23,6 @@ import ( cfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sighupio/furyctl/internal/configuration" "github.com/sirupsen/logrus" - "google.golang.org/appengine/log" ) // VSphere represents the VSphere provisioner @@ -465,7 +464,7 @@ func createPKI(workingDirectory string) error { ) downloadPath := filepath.Join(workingDirectory, "furyagent") - log.Infof("Download furyagent: %v", downloadPath) + logrus.Infof("Download furyagent: %v", downloadPath) if err := os.MkdirAll(downloadPath, 0755); err != nil { return err diff --git a/internal/distribution/model.go b/internal/distribution/model.go new file mode 100644 index 000000000..99e2f7b23 --- /dev/null +++ b/internal/distribution/model.go @@ -0,0 +1,78 @@ +package distribution + +import "github.com/sighupio/furyctl/internal/semver" + +type FuryctlConfig struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Spec struct { + DistributionVersion semver.Version `yaml:"distributionVersion"` + } `yaml:"spec"` +} + +func (c *FuryctlConfig) UnmarshalYAML(unmarshal func(any) error) error { + type rawFuryctlConfig FuryctlConfig + raw := rawFuryctlConfig{} + if err := unmarshal(&raw); err != nil { + return err + } + *c = FuryctlConfig(raw) + + distroVersion, err := semver.NewVersion(string(c.Spec.DistributionVersion)) + if err != nil { + return err + } + + c.Spec.DistributionVersion = distroVersion + + return nil +} + +type Manifest struct { + Version semver.Version `yaml:"version"` + Modules struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + } `yaml:"modules"` + Kubernetes struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + } `yaml:"kubernets"` + FuryctlSchemas struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + } `yaml:"furyctlSchemas"` + Tools struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + } `yaml:"tools"` +} + +func (m *Manifest) UnmarshalYAML(unmarshal func(any) error) error { + type rawKfdManifest Manifest + raw := rawKfdManifest{} + if err := unmarshal(&raw); err != nil { + return err + } + *m = Manifest(raw) + + version, err := semver.NewVersion(m.Version.String()) + if err != nil { + return err + } + + m.Version = version + + return nil +} diff --git a/internal/semver/compare.go b/internal/semver/compare.go new file mode 100644 index 000000000..40194114d --- /dev/null +++ b/internal/semver/compare.go @@ -0,0 +1,67 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" +) + +var ErrInvalidSemver = fmt.Errorf("invalid semantic version") + +func NewVersion(v string) (Version, error) { + if !isValid(v) { + return "", fmt.Errorf("%w: %s", ErrInvalidSemver, v) + } + + return Version(v), nil +} + +type Version string + +func (s Version) String() string { + return string(s) +} + +// SameMinor takes two versions and tell if they are part of the same minor +func SameMinor(a, b Version) bool { + if a == b { + return true + } + + ap := strings.Split(a.String(), ".") + ma := fmt.Sprintf("%s.%s", ap[0], ap[1]) + + bp := strings.Split(b.String(), ".") + mb := fmt.Sprintf("%s.%s", bp[0], bp[1]) + + return ma == mb +} + +func isValid(v string) bool { + if !strings.HasPrefix(v, "v") { + return false + } + + v = strings.TrimPrefix(v, "v") + + parts := strings.Split(v, ".") + + if len(parts) != 3 { + return false + } + + if _, err := strconv.Atoi(parts[0]); err != nil { + return false + } + + if _, err := strconv.Atoi(parts[1]); err != nil { + return false + } + + if _, err := strconv.Atoi(parts[2]); err != nil { + return false + } + + return true + +} diff --git a/internal/template/model.go b/internal/template/model.go index 0cd12e5a0..8162765ab 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -206,7 +206,7 @@ func (tm *Model) generateContext() (map[string]map[any]any, error) { cPath = v } - yamlConfig, err := yaml2.FromFile[map[any]any](cPath) + yamlConfig, err := yaml2.FromFileV2[map[any]any](cPath) if err != nil { return nil, err } diff --git a/internal/yaml/yaml.go b/internal/yaml/yaml.go index a294ae696..6c5fa2faf 100644 --- a/internal/yaml/yaml.go +++ b/internal/yaml/yaml.go @@ -5,20 +5,44 @@ package yaml import ( - "gopkg.in/yaml.v2" "os" + + v2 "gopkg.in/yaml.v2" + v3 "gopkg.in/yaml.v3" ) -func FromFile[T any](path string) (T, error) { - var yamlRes T +func FromFileV2[T any](path string) (T, error) { + var data T res, err := os.ReadFile(path) if err != nil { - return yamlRes, err + return data, err + } + + err = v2.Unmarshal(res, &data) + + return data, err +} + +func MarshalV2(in any) ([]byte, error) { + return v2.Marshal(in) +} + +func FromFileV3[T any](file string) (T, error) { + var data T + + res, err := os.ReadFile(file) + if err != nil { + return data, err } - err = yaml.Unmarshal(res, &yamlRes) + if err := v3.Unmarshal(res, &data); err != nil { + return data, err + } - return yamlRes, err + return data, err +} +func MarshalV3(in any) ([]byte, error) { + return v3.Marshal(in) } diff --git a/internal/yaml/yaml_test.go b/internal/yaml/yaml_test.go index 4bb8097ff..98970d123 100644 --- a/internal/yaml/yaml_test.go +++ b/internal/yaml/yaml_test.go @@ -5,18 +5,19 @@ package yaml_test import ( - yaml2 "github.com/sighupio/furyctl/internal/yaml" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" "os" "testing" + + yaml "github.com/sighupio/furyctl/internal/yaml" + "github.com/stretchr/testify/assert" + yaml2 "gopkg.in/yaml.v2" ) type TestYaml struct { Test string `yaml:"test"` } -func TestFromFile(t *testing.T) { +func TestFromFileV2(t *testing.T) { test := TestYaml{ "test", } @@ -29,7 +30,7 @@ func TestFromFile(t *testing.T) { assert.NoError(t, err) - testBytes, err := yaml.Marshal(test) + testBytes, err := yaml2.Marshal(test) assert.NoError(t, err) @@ -39,7 +40,7 @@ func TestFromFile(t *testing.T) { defer os.RemoveAll(path) - testRes, err := yaml2.FromFile[TestYaml](path + "/test.yaml") + testRes, err := yaml.FromFileV2[TestYaml](path + "/test.yaml") assert.NoError(t, err) From 1076c18847b430b218044f3d006533f390982790 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 16 Sep 2022 12:43:34 +0200 Subject: [PATCH 009/383] feat: added unit tests to validate/util --- cmd/validate/util.go | 14 ++- cmd/validate/util_test.go | 180 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 cmd/validate/util_test.go diff --git a/cmd/validate/util.go b/cmd/validate/util.go index 0f947e809..a9ee60cd3 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -29,13 +29,23 @@ var ( ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) -func getSchemaPath(basePath string, conf distribution.FuryctlConfig) string { +func getSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, error) { avp := strings.Split(conf.ApiVersion, "/") + + if len(avp) < 2 { + return "", fmt.Errorf("invalid apiVersion: %s", conf.ApiVersion) + } + ns := strings.Replace(avp[0], ".sighup.io", "", 1) ver := avp[1] + + if conf.Kind == "" { + return "", fmt.Errorf("kind is empty") + } + filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind), ns, ver) - return filepath.Join(basePath, "schemas", filename) + return filepath.Join(basePath, "schemas", filename), nil } func getDefaultPath(basePath string) string { diff --git a/cmd/validate/util_test.go b/cmd/validate/util_test.go new file mode 100644 index 000000000..3dc81488a --- /dev/null +++ b/cmd/validate/util_test.go @@ -0,0 +1,180 @@ +package validate + +import ( + "fmt" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" + "os" + "path/filepath" + "testing" +) + +func TestGetSchemaPath(t *testing.T) { + tests := []struct { + name string + basePath string + conf distribution.FuryctlConfig + want string + wantErr error + }{ + { + name: "test with base path", + basePath: "testpath", + conf: distribution.FuryctlConfig{ + ApiVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: struct { + DistributionVersion semver.Version `yaml:"distributionVersion"` + }{}, + }, + want: fmt.Sprintf("%s", filepath.Join( + "testpath", + "schemas", + "ekscluster-kfd-v1alpha2.json", + )), + wantErr: nil, + }, + { + name: "test without base path", + basePath: "", + conf: distribution.FuryctlConfig{ + ApiVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: struct { + DistributionVersion semver.Version `yaml:"distributionVersion"` + }{}, + }, + want: fmt.Sprintf("%s", filepath.Join("schemas", "ekscluster-kfd-v1alpha2.json")), + wantErr: nil, + }, + { + name: "test with invalid apiVersion", + basePath: "", + conf: distribution.FuryctlConfig{ + ApiVersion: "", + Kind: "EKSCluster", + Spec: struct { + DistributionVersion semver.Version `yaml:"distributionVersion"` + }{}, + }, + want: "", + wantErr: fmt.Errorf("invalid apiVersion: "), + }, + { + name: "test with invalid kind", + basePath: "", + conf: distribution.FuryctlConfig{ + ApiVersion: "kfd.sighup.io/v1alpha2", + Kind: "", + Spec: struct { + DistributionVersion semver.Version `yaml:"distributionVersion"` + }{}, + }, + want: "", + wantErr: fmt.Errorf("kind is empty"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getSchemaPath(tt.basePath, tt.conf) + if err != nil { + if err.Error() != tt.wantErr.Error() { + t.Errorf("getSchemaPath() error = %v, wantErr %v", err, tt.wantErr) + } + + return + } + + if got != tt.want { + t.Errorf("getSchemaPath() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDownloadDirectory(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-download-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + src, err := os.Create(filepath.Join(tmpDir, "test.txt")) + if err != nil { + t.Fatalf("error creating temp input file: %v", err) + } + + defer func() { + src.Close() + _ = os.RemoveAll(tmpDir) + }() + + dlDir, err := downloadDirectory(tmpDir) + if err != nil { + t.Fatalf("error downloading directory: %v", err) + } + + _, err = os.Stat(filepath.Join(dlDir, "test.txt")) + if err != nil { + t.Fatalf("error checking downloaded file: %v", err) + } +} + +func TestClientGet(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-clientget-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + in := filepath.Join(tmpDir, "in") + out := filepath.Join(tmpDir, "out") + + if err := os.MkdirAll(in, 0755); err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + src, err := os.Create(filepath.Join(in, "test.txt")) + if err != nil { + t.Fatalf("error creating temp input file: %v", err) + } + + defer func() { + src.Close() + _ = os.RemoveAll(tmpDir) + }() + + err = clientGet(in, out) + if err != nil { + t.Fatalf("error getting directory: %v", err) + } + + _, err = os.Stat(filepath.Join(out, "test.txt")) + if err != nil { + t.Fatalf("error getting file: %v", err) + } +} + +func TestUrlHasForcedProtocol(t *testing.T) { + tests := []struct { + name string + url string + want bool + }{ + { + name: "test with http", + url: "http::test.com", + want: true, + }, + { + name: "test without protocol", + url: "test.com", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := urlHasForcedProtocol(tt.url); got != tt.want { + t.Errorf("urlHasForcedProtocol() = %v, want %v", got, tt.want) + } + }) + } +} From 1ff3c24d7e670b6f0373ca5d5a9383650326f9d8 Mon Sep 17 00:00:00 2001 From: omissis Date: Fri, 16 Sep 2022 14:39:58 +0200 Subject: [PATCH 010/383] chore: refactor json schema loader --- internal/schema/santhosh/loader.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/schema/santhosh/loader.go b/internal/schema/santhosh/loader.go index b4e9cec9d..1641a19d8 100644 --- a/internal/schema/santhosh/loader.go +++ b/internal/schema/santhosh/loader.go @@ -10,11 +10,11 @@ import ( ) var ( - ErrCannotLoadSchema = errors.New("failed to read schema file") + ErrCannotLoadSchema = errors.New("failed to load schema file") ) -func LoadSchema(schemaPath string) (schema *jsonschema.Schema, errSchema error) { - berr := fmt.Errorf("%w: '%s'", ErrCannotLoadSchema, schemaPath) +func LoadSchema(schemaPath string) (*jsonschema.Schema, error) { + berr := fmt.Errorf("%w '%s'", ErrCannotLoadSchema, schemaPath) data, err := os.ReadFile(schemaPath) if err != nil { @@ -26,8 +26,8 @@ func LoadSchema(schemaPath string) (schema *jsonschema.Schema, errSchema error) return nil, fmt.Errorf("%w: %v", berr, err) } - schema, errSchema = compiler.Compile(schemaPath) - if errSchema != nil { + schema, err := compiler.Compile(schemaPath) + if err != nil { return nil, fmt.Errorf("%w: %v", berr, err) } From b77c48f0d8206ccedc8d61f5792466e7981e861b Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 10:39:56 +0200 Subject: [PATCH 011/383] chore: cleanup code and deps --- cmd/validate/config.go | 6 +++++- go.mod | 4 +++- go.sum | 5 +---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index a3695b746..ebf57c36c 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -78,7 +78,11 @@ func NewConfigCmd(version string) *cobra.Command { ) } - schemaPath := getSchemaPath(repoPath, minimalConf) + schemaPath, err := getSchemaPath(repoPath, minimalConf) + if err != nil { + return err + } + defaultPath := getDefaultPath(repoPath) defaultedFuryctlPath, err := mergeConfigAndDefaults(furyctlPath, defaultPath) diff --git a/go.mod b/go.mod index cd6c66389..42e910763 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,8 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) +require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + require ( cloud.google.com/go v0.81.0 // indirect cloud.google.com/go/storage v1.10.0 // indirect @@ -42,6 +44,7 @@ require ( github.com/gobuffalo/packd v1.0.1 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect @@ -93,7 +96,6 @@ require ( google.golang.org/grpc v1.38.0 // indirect google.golang.org/protobuf v1.26.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) go 1.18 diff --git a/go.sum b/go.sum index e95d7c9b2..bc60eb303 100644 --- a/go.sum +++ b/go.sum @@ -178,6 +178,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -217,8 +218,6 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI= -github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY= -github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk= github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -453,8 +452,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= From 2cea4b661da09600bd96ace70f0c9e02e928a5ff Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 11:03:53 +0200 Subject: [PATCH 012/383] chore: add missing license banners --- cmd/validate.go | 4 ++++ cmd/validate/config.go | 4 ++++ cmd/validate/util.go | 4 ++++ cmd/validate/util_test.go | 9 +++++++-- cmd/version.go | 4 ++++ internal/distribution/model.go | 4 ++++ internal/schema/santhosh/loader.go | 4 ++++ internal/semver/compare.go | 4 ++++ 8 files changed, 35 insertions(+), 2 deletions(-) diff --git a/cmd/validate.go b/cmd/validate.go index 86a1fd9b7..50ef6d30c 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package cmd import ( diff --git a/cmd/validate/config.go b/cmd/validate/config.go index ebf57c36c..ba4afea82 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package validate import ( diff --git a/cmd/validate/util.go b/cmd/validate/util.go index a9ee60cd3..827e9af3b 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package validate import ( diff --git a/cmd/validate/util_test.go b/cmd/validate/util_test.go index 3dc81488a..89ec91346 100644 --- a/cmd/validate/util_test.go +++ b/cmd/validate/util_test.go @@ -1,12 +1,17 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package validate import ( "fmt" - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/semver" "os" "path/filepath" "testing" + + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" ) func TestGetSchemaPath(t *testing.T) { diff --git a/cmd/version.go b/cmd/version.go index db7c42030..b2b5dab83 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package cmd import ( diff --git a/internal/distribution/model.go b/internal/distribution/model.go index 99e2f7b23..686542f10 100644 --- a/internal/distribution/model.go +++ b/internal/distribution/model.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package distribution import "github.com/sighupio/furyctl/internal/semver" diff --git a/internal/schema/santhosh/loader.go b/internal/schema/santhosh/loader.go index 1641a19d8..a4efed6b7 100644 --- a/internal/schema/santhosh/loader.go +++ b/internal/schema/santhosh/loader.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package santhosh import ( diff --git a/internal/semver/compare.go b/internal/semver/compare.go index 40194114d..cd684fb60 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package semver import ( From 76b16c82b6fbe4db02936cd07a1c732b022e34dc Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 13:18:04 +0200 Subject: [PATCH 013/383] fix: change version comparison of furyctl.yaml and kfd.yaml to patch level --- cmd/validate/config.go | 2 +- internal/semver/compare.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index ba4afea82..ff72b1193 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -74,7 +74,7 @@ func NewConfigCmd(version string) *cobra.Command { return err } - if !semver.SameMinor(furyctlConfVersion, kfdManifest.Version) { + if !semver.SamePatch(furyctlConfVersion, kfdManifest.Version) { return fmt.Errorf( "minor versions mismatch: furyctl.yaml has %s, but furyctl has %s", furyctlConfVersion.String(), diff --git a/internal/semver/compare.go b/internal/semver/compare.go index cd684fb60..09c7ef36d 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -26,6 +26,21 @@ func (s Version) String() string { return string(s) } +// SamePatch takes two versions and tell if they are part of the same patch +func SamePatch(a, b Version) bool { + if a == b { + return true + } + + ap := strings.Split(a.String(), ".") + ma := fmt.Sprintf("%s.%s.%s", ap[0], ap[1], ap[2]) + + bp := strings.Split(b.String(), ".") + mb := fmt.Sprintf("%s.%s.%s", bp[0], bp[1], bp[2]) + + return ma == mb +} + // SameMinor takes two versions and tell if they are part of the same minor func SameMinor(a, b Version) bool { if a == b { From 596a5d486eb522d6e5e2d8922cfd392d6775da31 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:52:41 +0200 Subject: [PATCH 014/383] feat: added validate dependencies sub command (#92) * chore: added unit tests to config.go * feat: added validate dependencies sub command * chore: added const errors * feat: added unit tests to dependencies.go * feat: added validate config e2e tests * chore: added missing licenses * chore: added validation's integration testing step * chore: updated test data from distribution * fix: integration tests for validation cmd * fix: version from furyctl * fix: add patch version comparison to the validate deps command * chore: implement CR suggestions * chore: refactorings and tests - add internal function to get cli flags - add integration test for validate dependencies - add --bin-path flag for validate dependencies * chore: add missing license banners Co-authored-by: omissis --- .drone.yml | 7 + .../furyctl-defaults.yaml | 192 +++ .../config-invalid-furyctl-yaml/furyctl.yaml | 329 +++++ .../config-invalid-furyctl-yaml/kfd.yaml | 26 + .../schemas/ekscluster-kfd-v1alpha2.json | 1314 +++++++++++++++++ .../furyctl-defaults.yaml | 192 +++ .../config-valid-furyctl-yaml/furyctl.yaml | 328 ++++ .../config-valid-furyctl-yaml/kfd.yaml | 26 + .../schemas/ekscluster-kfd-v1alpha2.json | 1314 +++++++++++++++++ .../test-data/dependencies-correct/ansible | 13 + .../test-data/dependencies-correct/furyagent | 5 + .../dependencies-correct/furyctl.yaml | 328 ++++ .../test-data/dependencies-correct/kfd.yaml | 26 + .../test-data/dependencies-correct/kubectl | 5 + .../test-data/dependencies-correct/kustomize | 5 + .../test-data/dependencies-correct/terraform | 6 + .../dependencies-missing/furyctl.yaml | 328 ++++ .../test-data/dependencies-missing/kfd.yaml | 26 + .../test-data/dependencies-wrong/ansible | 13 + .../test-data/dependencies-wrong/furyagent | 5 + .../test-data/dependencies-wrong/furyctl.yaml | 328 ++++ .../test-data/dependencies-wrong/kfd.yaml | 26 + .../test-data/dependencies-wrong/kubectl | 5 + .../test-data/dependencies-wrong/kustomize | 5 + .../test-data/dependencies-wrong/terraform | 6 + .../integration/validation-cmd/tests.sh | 147 ++ cmd/validate.go | 1 + cmd/validate/config.go | 12 +- cmd/validate/config_test.go | 346 +++++ cmd/validate/dependencies.go | 382 +++++ cmd/validate/dependencies_test.go | 644 ++++++++ cmd/validate/util.go | 34 +- go.mod | 13 +- go.sum | 9 +- internal/distribution/model.go | 12 +- internal/distribution/resources.go | 7 + 36 files changed, 6446 insertions(+), 19 deletions(-) create mode 100644 automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json create mode 100644 automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-correct/ansible create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-correct/furyagent create mode 100644 automated-tests/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-correct/kubectl create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-correct/kustomize create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-correct/terraform create mode 100644 automated-tests/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/ansible create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/furyagent create mode 100644 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml create mode 100644 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kubectl create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kustomize create mode 100755 automated-tests/integration/validation-cmd/test-data/dependencies-wrong/terraform create mode 100644 automated-tests/integration/validation-cmd/tests.sh create mode 100644 cmd/validate/config_test.go create mode 100644 cmd/validate/dependencies.go create mode 100644 cmd/validate/dependencies_test.go create mode 100644 internal/distribution/resources.go diff --git a/.drone.yml b/.drone.yml index 883818dca..27c5339a2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -100,6 +100,11 @@ steps: commands: - bats -t ./automated-tests/integration/template-engine/tests.sh + - <<: *integration + name: integration-validation-cmd + commands: + - bats -t ./automated-tests/integration/validation-cmd/tests.sh + - &e2e name: e2e-gcp image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.3.0_v1.24.9_20.04 @@ -188,6 +193,8 @@ steps: - integration-gcp-gke - integration-aws-eks - integration-vsphere + - integration-template-engine + - integration-validation-cmd - e2e-aws - e2e-gcp - e2e-vsphere diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml new file mode 100644 index 000000000..e1454aeaa --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: example.dev + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789012:role/example-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: [] + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789012:role/example-velero + region: eu-west-1 + bucket: example-velero + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: "" + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: "" + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: "" + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/example + iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml new file mode 100644 index 000000000..2affdfca1 --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml @@ -0,0 +1,329 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + # the name will be used as a prefix/suffix for all the managed resources + name: awesome-cluster-staging +spec: + # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions + distributionVersion: "v1.23.3" + test: test # should make the validation fail + # Under the hood, furyctl uses other tools like terraform, kustomize, etc + toolsConfiguration: + terraform: + state: + s3: + bucketName: awesome-bucket-created-outside-furyctl + # changed from key, because each terraform project state will be placed in the directory defined by the prefix + keyPrefix: furyctl/ + region: eu-west-1 + # gcs: + # bucketName: + # ... + # terraformCloud: + # ... + # tags to apply to all resources in AWS + tags: + env: "staging" + k8s: "awesome" + # whathever: "something" + infrastructure: + vpc: + # new network configuration, with a more hierarchical structure + network: + cidr: 10.1.0.0/16 + subnetsCidrs: + private: + - 10.1.0.0/20 + - 10.1.16.0/20 + - 10.1.32.0/20 + public: + - 10.1.48.0/24 + - 10.1.49.0/24 + - 10.1.50.0/24 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase + # and will be automatically gathered from the output of the bootstrap phase + vpcId: vpc-0f92da9b4a2089963 + # optional, see the above comment + subnetIds: + - subnet-0ab84702287e38ccb + - subnet-0ae4e9199d9192226 + - subnet-01787e8da51e4f070 + apiServerEndpointAccess: + # by default all clusters are created with a private control plane - (needs to be added to the installer's options) + type: "private" + # cidr allowed to talk with the apiServer + allowedCidrs: + - 10.1.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + # optional, was OS, and version was removed + ami: + id: null + owner: null + # sizing, with a more hierarchical structure + size: + min: 1 + max: 3 + instance: + type: t3.micro + # optional, this enable spot instances on the ASG + spot: false + volumeSize: 50 + # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + # labels that are added to the nodes + labels: + nodepool: worker + node.kubernetes.io/role: worker + # optional, taints added to the nodes + taints: + - node.kubernetes.io/role=worker:NoSchedule + # tags added to the ASG + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + # additional rules added to the ASG nodes security group + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + # aws-auth configmap definiton + awsAuth: + # Additional AWS account id to add to the aws-auth configmap, optional + additionalAccounts: + - "777777777777" + - "88888888888" + # Additional users to add to the aws-auth configmap, optional + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + # Additional roles to add to the aws-auth configmap, optional + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules + distribution: + common: + # relativeVendorPath: "../../vendor" should be automatically set by furyctl + # provider: + # type: eks automatically set by furyctl + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + # the base ingress domain for all the ingresses in the cluster + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: test + key: test + ca: test + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, should be taken automatically from the role generation using module.ingress.dns.public settings + # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns + dns: + public: + enabled: true + name: "fury-demo.sighup.io" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + private: + enabled: true + name: "internal.fury-demo.sighup.io" + # optional, if vpc is enabled: false + vpcId: "vpc123123123123" + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: [] + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: test + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: test + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: test + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: test + dex: + # see dex documentation for more information + connectors: + - type: github + # Required field for connector id. + id: github + # Required field for connector name. + name: GitHub + config: + # Credentials can be string literals or pulled from the environment. + clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} + clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + redirectURI: https://login.fury-demo.sighup.io/callback + loadAllGroups: false + teamNameField: slug + useLoginAsID: false diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml new file mode 100644 index 000000000..88c06c2e8 --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.23.3 +modules: + auth: v0.0.1 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 +kubernetes: + eks: + version: 1.23 + installer: v1.9.1 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + ansible: 2.11.2 + furyagent: 0.3.0 + kubectl: 1.23.7 + kustomize: 3.10.0 + terraform: 0.15.4 diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json new file mode 100644 index 000000000..3edea0860 --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json @@ -0,0 +1,1314 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["EKSCluster"] + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "$defs": { + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "distributionVersion": { + "$ref": "#/$defs/Types.SemVer" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "toolsConfiguration": { + "$ref": "#/$defs/Spec.ToolsConfiguration" + }, + "infrastructure": { + "$ref": "#/$defs/Spec.Infrastructure" + }, + "kubernetes": { + "type": "object", + "additionalProperties": true + }, + "distribution": { + "$ref": "#/$defs/Spec.Distribution" + } + }, + "required": [ + "distributionVersion", + "infrastructure", + "kubernetes", + "toolsConfiguration" + ] + }, + + "Spec.ToolsConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" + } + }, + "required": [ + "terraform" + ] + }, + "Spec.ToolsConfiguration.Terraform": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" + } + }, + "required": [ + "state" + ] + }, + "Spec.ToolsConfiguration.Terraform.State": { + "type": "object", + "additionalProperties": false, + "properties": { + "s3": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" + } + }, + "required": [ + "s3" + ] + }, + "Spec.ToolsConfiguration.Terraform.State.S3": { + "type": "object", + "additionalProperties": false, + "properties": { + "bucketName": { + "type": "string" + }, + "keyPrefix": { + "type": "string" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "bucketName", + "keyPrefix", + "region" + ] + }, + + "Spec.Infrastructure": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpc": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc" + } + }, + "required": [ + "vpc" + ] + }, + "Spec.Infrastructure.Vpc": { + "type": "object", + "additionalProperties": false, + "properties": { + "network": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" + } + }, + "required": [ + "network" + ] + }, + "Spec.Infrastructure.Vpc.Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "subnetsCidrs": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" + } + }, + "required": [ + "cidr", + "subnetsCidrs" + ] + }, + "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { + "type": "object", + "additionalProperties": false, + "properties": { + "private": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "public": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "private", + "public" + ] + }, + "Spec.Infrastructure.Vpc.Vpn": { + "type": "object", + "additionalProperties": false, + "properties": { + "instances": { + "type": "integer" + }, + "port": { + "$ref": "#/$defs/Types.TcpPort" + }, + "instanceType": { + "type": "string" + }, + "diskSize": { + "type": "integer" + }, + "operatorName": { + "type": "string" + }, + "dhParamsBits": { + "type": "integer" + }, + "vpnClientsSubnetCidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "ssh": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + } + }, + "required": [ + "dhParamsBits", + "diskSize", + "instances", + "instanceType", + "operatorName", + "port", + "ssh", + "vpnClientsSubnetCidr" + ] + }, + "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "type": "object", + "additionalProperties": false, + "properties": { + "publicKeys": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + } + }, + "githubUsersName": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedFromCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedFromCidrs", + "githubUsersName", + "publicKeys" + ] + }, + + "Spec.Kubernetes": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "apiServerEndpointAccess": { + "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + }, + "nodeAllowedSshPublicKey": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + }, + "nodePools": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool" + } + }, + "awsAuth": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" + } + }, + "required": [ + "apiServerEndpointAccess", + "awsAuth", + "nodeAllowedSshPublicKey", + "nodePools", + "subnetIds", + "vpcId" + ] + }, + "Spec.Kubernetes.APIServerEndpointAccess": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "public", + "private", + "public_and_private" + ] + }, + "allowedCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedCidrs", + "type" + ] + }, + "Spec.Kubernetes.NodePool": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "ami": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" + }, + "size": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" + }, + "instance": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" + }, + "attachedTargetGroups": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "taints": { + "$ref": "#/$defs/Types.KubeTaints" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "additionalFirewallRules": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" + } + } + }, + "required": [ + "additionalFirewallRules", + "ami", + "attachedTargetGroups", + "instance", + "labels", + "name", + "size", + "tags", + "taints" + ] + }, + "Spec.Kubernetes.NodePool.Ami": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "owner" + ] + }, + "Spec.Kubernetes.NodePool.Instance": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "spot": { + "type": "boolean" + }, + "volumeSize": { + "type": "integer" + } + }, + "required": [ + "spot", + "type", + "volumeSize" + ] + }, + "Spec.Kubernetes.NodePool.Size": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer", + "minimum": 1 + }, + "max": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "max", + "min" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/$defs/Types.TcpPort" + }, + "to": { + "$ref": "#/$defs/Types.TcpPort" + } + }, + "required": [ + "from", + "to" + ] + }, + "Spec.Kubernetes.AwsAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" + } + } + }, + "required": [ + "additionalAccounts", + "roles", + "users" + ] + }, + "Spec.Kubernetes.AwsAuth.Role": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "rolearn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "rolearn", + "username" + ] + }, + "Spec.Kubernetes.AwsAuth.User": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "userarn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "userarn", + "username" + ] + }, + + "Spec.Distribution": { + "type": "object", + "additionalProperties": false, + "properties": { + "common": { + "$ref": "#/$defs/Spec.Distribution.Common" + }, + "modules": { + "$ref": "#/$defs/Spec.Distribution.Modules" + } + }, + "required": [ + "common", + "modules" + ] + }, + "Spec.Distribution.Common": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Common.Provider" + }, + "relativeVendorPath": { + "type": "string" + } + }, + "required": [ + "nodeSelector", + "tolerations", + "provider", + "relativeVendorPath" + ] + }, + "Spec.Distribution.Common.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth" + }, + "dr": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr" + }, + "ingress": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" + }, + "logging": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging" + }, + "monitoring": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" + }, + "policy": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy" + }, + "aws": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws" + } + }, + "required": [ + "auth", + "dr", + "ingress", + "logging", + "monitoring", + "policy" + ] + }, + "Spec.Distribution.Modules.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "baseDomain": { + "type": "string" + }, + "nginx": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" + }, + "certManager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CERTManager" + }, + "dns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" + } + }, + "required": [ + "baseDomain", + "certManager", + "dns", + "nginx", + "overrides" + ] + }, + "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecastle": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" + } + } + }, + "Spec.Distribution.Modules.Ingress.Nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "dual"] + }, + "tls": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + } + }, + "required": [ + "tls", + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "enum": ["certManager", "secret", "none"] + }, + "secret": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" + } + }, + "required": [ + "provider", + "secret" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ca": { + "type": "string" + } + }, + "required": [ + "ca", + "cert", + "key" + ] + }, + "Spec.Distribution.Modules.Ingress.CERTManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "clusterIssuer": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer" + } + }, + "required": [ + "clusterIssuer" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["dns01", "http01"] + }, + "route53": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" + } + }, + "required": [ + "name", + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "hostedZoneId": { + "type": "string" + } + }, + "required": [ + "hostedZoneId", + "iamRoleArn", + "region" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "public": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" + }, + "private": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + } + }, + "required": [ + "public", + "private" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Public": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "name", + "create" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Private": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "vpcId": { + "type": "string" + } + }, + "required": [ + "enabled", + "name", + "vpcId" + ] + }, + "Spec.Distribution.Modules.Logging": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "opensearch": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + } + } + }, + "Spec.Distribution.Modules.Logging.Opensearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "triple"] + }, + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "storage_request": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Monitoring": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "prometheus": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" + } + } + }, + "Spec.Distribution.Modules.Monitoring.Prometheus": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "gatekeeper": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" + } + } + }, + "Spec.Distribution.Modules.Policy.Gatekeeper": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalExcludedNamespaces": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Spec.Distribution.Modules.Dr": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "velero": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" + } + } + }, + "Spec.Distribution.Modules.Dr.Velero": { + "type": "object", + "additionalProperties": false, + "properties": { + "eks": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + } + } + }, + "Spec.Distribution.Modules.Dr.Velero.Eks": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "bucket": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" + }, + "pomerium": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" + }, + "dex": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" + } + } + }, + + "Spec.Distribution.Modules.Auth.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingresses" + } + } + } + }, + "Spec.Distribution.Modules.Auth.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "host", + "ingressClass" + ] + }, + "Spec.Distribution.Modules.Auth.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["none", "basicAuth", "sso"] + }, + "basicAuth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" + } + }, + "required": [ + "secrets" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "COOKIE_SECRET": { + "type": "string" + }, + "IDP_CLIENT_SECRET": { + "type": "string" + }, + "SHARED_SECRET": { + "type": "string" + } + }, + "required": [ + "COOKIE_SECRET", + "IDP_CLIENT_SECRET", + "SHARED_SECRET" + ] + }, + "Spec.Distribution.Modules.Auth.Dex": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectors": { + "type": "array" + } + } + }, + "Spec.Distribution.Modules.Aws": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "clusterAutoscaler": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + } + } + }, + "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeGroupAutoDiscovery": { + "type": "string" + }, + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn", + "nodeGroupAutoDiscovery" + ] + }, + + "Types.SemVer": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "Types.IpAddress": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" + }, + "Types.Cidr": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" + }, + "Types.FileRef": { + "type": "string", + "pattern": "^\\{file\\:\\/\\/.+\\}$" + }, + "Types.EnvRef": { + "type": "string", + "pattern": "\\{^env\\:\\/\\/.*\\}$" + }, + "Types.TcpPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "Types.SshPubKey": { + "type": "string", + "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" + }, + "Types.Uri": { + "type": "string", + "pattern": "^(http|https)\\:\\/\\/.+$" + }, + "Types.AwsArn": { + "type": "string", + "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{16})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{16}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "enum": ["tcp", "udp", "icmp", "icmpv6", "-1"] + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" + } + } + } + }, + "Types.FuryModuleOverridesIngresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml new file mode 100644 index 000000000..e1454aeaa --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: example.dev + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789012:role/example-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: [] + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789012:role/example-velero + region: eu-west-1 + bucket: example-velero + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: "" + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: "" + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: "" + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/example + iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml new file mode 100644 index 000000000..d1cabab7e --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml @@ -0,0 +1,328 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + # the name will be used as a prefix/suffix for all the managed resources + name: awesome-cluster-staging +spec: + # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions + distributionVersion: "v1.23.3" + # Under the hood, furyctl uses other tools like terraform, kustomize, etc + toolsConfiguration: + terraform: + state: + s3: + bucketName: awesome-bucket-created-outside-furyctl + # changed from key, because each terraform project state will be placed in the directory defined by the prefix + keyPrefix: furyctl/ + region: eu-west-1 + # gcs: + # bucketName: + # ... + # terraformCloud: + # ... + # tags to apply to all resources in AWS + tags: + env: "staging" + k8s: "awesome" + # whathever: "something" + infrastructure: + vpc: + # new network configuration, with a more hierarchical structure + network: + cidr: 10.1.0.0/16 + subnetsCidrs: + private: + - 10.1.0.0/20 + - 10.1.16.0/20 + - 10.1.32.0/20 + public: + - 10.1.48.0/24 + - 10.1.49.0/24 + - 10.1.50.0/24 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase + # and will be automatically gathered from the output of the bootstrap phase + vpcId: vpc-0f92da9b4a2089963 + # optional, see the above comment + subnetIds: + - subnet-0ab84702287e38ccb + - subnet-0ae4e9199d9192226 + - subnet-01787e8da51e4f070 + apiServerEndpointAccess: + # by default all clusters are created with a private control plane - (needs to be added to the installer's options) + type: "private" + # cidr allowed to talk with the apiServer + allowedCidrs: + - 10.1.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + # optional, was OS, and version was removed + ami: + id: null + owner: null + # sizing, with a more hierarchical structure + size: + min: 1 + max: 3 + instance: + type: t3.micro + # optional, this enable spot instances on the ASG + spot: false + volumeSize: 50 + # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + # labels that are added to the nodes + labels: + nodepool: worker + node.kubernetes.io/role: worker + # optional, taints added to the nodes + taints: + - node.kubernetes.io/role=worker:NoSchedule + # tags added to the ASG + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + # additional rules added to the ASG nodes security group + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + # aws-auth configmap definiton + awsAuth: + # Additional AWS account id to add to the aws-auth configmap, optional + additionalAccounts: + - "777777777777" + - "88888888888" + # Additional users to add to the aws-auth configmap, optional + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + # Additional roles to add to the aws-auth configmap, optional + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules + distribution: + common: + # relativeVendorPath: "../../vendor" should be automatically set by furyctl + # provider: + # type: eks automatically set by furyctl + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + # the base ingress domain for all the ingresses in the cluster + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: test + key: test + ca: test + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, should be taken automatically from the role generation using module.ingress.dns.public settings + # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns + dns: + public: + enabled: true + name: "fury-demo.sighup.io" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + private: + enabled: true + name: "internal.fury-demo.sighup.io" + # optional, if vpc is enabled: false + vpcId: "vpc123123123123" + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: [] + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: test + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: test + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: test + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: test + dex: + # see dex documentation for more information + connectors: + - type: github + # Required field for connector id. + id: github + # Required field for connector name. + name: GitHub + config: + # Credentials can be string literals or pulled from the environment. + clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} + clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + redirectURI: https://login.fury-demo.sighup.io/callback + loadAllGroups: false + teamNameField: slug + useLoginAsID: false \ No newline at end of file diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml new file mode 100644 index 000000000..88c06c2e8 --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.23.3 +modules: + auth: v0.0.1 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 +kubernetes: + eks: + version: 1.23 + installer: v1.9.1 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + ansible: 2.11.2 + furyagent: 0.3.0 + kubectl: 1.23.7 + kustomize: 3.10.0 + terraform: 0.15.4 diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json new file mode 100644 index 000000000..3edea0860 --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json @@ -0,0 +1,1314 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["EKSCluster"] + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "$defs": { + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "distributionVersion": { + "$ref": "#/$defs/Types.SemVer" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "toolsConfiguration": { + "$ref": "#/$defs/Spec.ToolsConfiguration" + }, + "infrastructure": { + "$ref": "#/$defs/Spec.Infrastructure" + }, + "kubernetes": { + "type": "object", + "additionalProperties": true + }, + "distribution": { + "$ref": "#/$defs/Spec.Distribution" + } + }, + "required": [ + "distributionVersion", + "infrastructure", + "kubernetes", + "toolsConfiguration" + ] + }, + + "Spec.ToolsConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" + } + }, + "required": [ + "terraform" + ] + }, + "Spec.ToolsConfiguration.Terraform": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" + } + }, + "required": [ + "state" + ] + }, + "Spec.ToolsConfiguration.Terraform.State": { + "type": "object", + "additionalProperties": false, + "properties": { + "s3": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" + } + }, + "required": [ + "s3" + ] + }, + "Spec.ToolsConfiguration.Terraform.State.S3": { + "type": "object", + "additionalProperties": false, + "properties": { + "bucketName": { + "type": "string" + }, + "keyPrefix": { + "type": "string" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "bucketName", + "keyPrefix", + "region" + ] + }, + + "Spec.Infrastructure": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpc": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc" + } + }, + "required": [ + "vpc" + ] + }, + "Spec.Infrastructure.Vpc": { + "type": "object", + "additionalProperties": false, + "properties": { + "network": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" + } + }, + "required": [ + "network" + ] + }, + "Spec.Infrastructure.Vpc.Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "subnetsCidrs": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" + } + }, + "required": [ + "cidr", + "subnetsCidrs" + ] + }, + "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { + "type": "object", + "additionalProperties": false, + "properties": { + "private": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "public": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "private", + "public" + ] + }, + "Spec.Infrastructure.Vpc.Vpn": { + "type": "object", + "additionalProperties": false, + "properties": { + "instances": { + "type": "integer" + }, + "port": { + "$ref": "#/$defs/Types.TcpPort" + }, + "instanceType": { + "type": "string" + }, + "diskSize": { + "type": "integer" + }, + "operatorName": { + "type": "string" + }, + "dhParamsBits": { + "type": "integer" + }, + "vpnClientsSubnetCidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "ssh": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + } + }, + "required": [ + "dhParamsBits", + "diskSize", + "instances", + "instanceType", + "operatorName", + "port", + "ssh", + "vpnClientsSubnetCidr" + ] + }, + "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "type": "object", + "additionalProperties": false, + "properties": { + "publicKeys": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + } + }, + "githubUsersName": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedFromCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedFromCidrs", + "githubUsersName", + "publicKeys" + ] + }, + + "Spec.Kubernetes": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "apiServerEndpointAccess": { + "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + }, + "nodeAllowedSshPublicKey": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + }, + "nodePools": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool" + } + }, + "awsAuth": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" + } + }, + "required": [ + "apiServerEndpointAccess", + "awsAuth", + "nodeAllowedSshPublicKey", + "nodePools", + "subnetIds", + "vpcId" + ] + }, + "Spec.Kubernetes.APIServerEndpointAccess": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "public", + "private", + "public_and_private" + ] + }, + "allowedCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedCidrs", + "type" + ] + }, + "Spec.Kubernetes.NodePool": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "ami": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" + }, + "size": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" + }, + "instance": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" + }, + "attachedTargetGroups": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "taints": { + "$ref": "#/$defs/Types.KubeTaints" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "additionalFirewallRules": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" + } + } + }, + "required": [ + "additionalFirewallRules", + "ami", + "attachedTargetGroups", + "instance", + "labels", + "name", + "size", + "tags", + "taints" + ] + }, + "Spec.Kubernetes.NodePool.Ami": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "owner" + ] + }, + "Spec.Kubernetes.NodePool.Instance": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "spot": { + "type": "boolean" + }, + "volumeSize": { + "type": "integer" + } + }, + "required": [ + "spot", + "type", + "volumeSize" + ] + }, + "Spec.Kubernetes.NodePool.Size": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer", + "minimum": 1 + }, + "max": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "max", + "min" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/$defs/Types.TcpPort" + }, + "to": { + "$ref": "#/$defs/Types.TcpPort" + } + }, + "required": [ + "from", + "to" + ] + }, + "Spec.Kubernetes.AwsAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" + } + } + }, + "required": [ + "additionalAccounts", + "roles", + "users" + ] + }, + "Spec.Kubernetes.AwsAuth.Role": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "rolearn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "rolearn", + "username" + ] + }, + "Spec.Kubernetes.AwsAuth.User": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "userarn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "userarn", + "username" + ] + }, + + "Spec.Distribution": { + "type": "object", + "additionalProperties": false, + "properties": { + "common": { + "$ref": "#/$defs/Spec.Distribution.Common" + }, + "modules": { + "$ref": "#/$defs/Spec.Distribution.Modules" + } + }, + "required": [ + "common", + "modules" + ] + }, + "Spec.Distribution.Common": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Common.Provider" + }, + "relativeVendorPath": { + "type": "string" + } + }, + "required": [ + "nodeSelector", + "tolerations", + "provider", + "relativeVendorPath" + ] + }, + "Spec.Distribution.Common.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth" + }, + "dr": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr" + }, + "ingress": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" + }, + "logging": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging" + }, + "monitoring": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" + }, + "policy": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy" + }, + "aws": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws" + } + }, + "required": [ + "auth", + "dr", + "ingress", + "logging", + "monitoring", + "policy" + ] + }, + "Spec.Distribution.Modules.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "baseDomain": { + "type": "string" + }, + "nginx": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" + }, + "certManager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CERTManager" + }, + "dns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" + } + }, + "required": [ + "baseDomain", + "certManager", + "dns", + "nginx", + "overrides" + ] + }, + "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecastle": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" + } + } + }, + "Spec.Distribution.Modules.Ingress.Nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "dual"] + }, + "tls": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + } + }, + "required": [ + "tls", + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "enum": ["certManager", "secret", "none"] + }, + "secret": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" + } + }, + "required": [ + "provider", + "secret" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ca": { + "type": "string" + } + }, + "required": [ + "ca", + "cert", + "key" + ] + }, + "Spec.Distribution.Modules.Ingress.CERTManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "clusterIssuer": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer" + } + }, + "required": [ + "clusterIssuer" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["dns01", "http01"] + }, + "route53": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" + } + }, + "required": [ + "name", + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "hostedZoneId": { + "type": "string" + } + }, + "required": [ + "hostedZoneId", + "iamRoleArn", + "region" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "public": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" + }, + "private": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + } + }, + "required": [ + "public", + "private" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Public": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "name", + "create" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Private": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "vpcId": { + "type": "string" + } + }, + "required": [ + "enabled", + "name", + "vpcId" + ] + }, + "Spec.Distribution.Modules.Logging": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "opensearch": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + } + } + }, + "Spec.Distribution.Modules.Logging.Opensearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "triple"] + }, + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "storage_request": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Monitoring": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "prometheus": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" + } + } + }, + "Spec.Distribution.Modules.Monitoring.Prometheus": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "gatekeeper": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" + } + } + }, + "Spec.Distribution.Modules.Policy.Gatekeeper": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalExcludedNamespaces": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Spec.Distribution.Modules.Dr": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "velero": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" + } + } + }, + "Spec.Distribution.Modules.Dr.Velero": { + "type": "object", + "additionalProperties": false, + "properties": { + "eks": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + } + } + }, + "Spec.Distribution.Modules.Dr.Velero.Eks": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "bucket": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" + }, + "pomerium": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" + }, + "dex": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" + } + } + }, + + "Spec.Distribution.Modules.Auth.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingresses" + } + } + } + }, + "Spec.Distribution.Modules.Auth.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "host", + "ingressClass" + ] + }, + "Spec.Distribution.Modules.Auth.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["none", "basicAuth", "sso"] + }, + "basicAuth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" + } + }, + "required": [ + "secrets" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "COOKIE_SECRET": { + "type": "string" + }, + "IDP_CLIENT_SECRET": { + "type": "string" + }, + "SHARED_SECRET": { + "type": "string" + } + }, + "required": [ + "COOKIE_SECRET", + "IDP_CLIENT_SECRET", + "SHARED_SECRET" + ] + }, + "Spec.Distribution.Modules.Auth.Dex": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectors": { + "type": "array" + } + } + }, + "Spec.Distribution.Modules.Aws": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "clusterAutoscaler": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + } + } + }, + "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeGroupAutoDiscovery": { + "type": "string" + }, + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn", + "nodeGroupAutoDiscovery" + ] + }, + + "Types.SemVer": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "Types.IpAddress": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" + }, + "Types.Cidr": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" + }, + "Types.FileRef": { + "type": "string", + "pattern": "^\\{file\\:\\/\\/.+\\}$" + }, + "Types.EnvRef": { + "type": "string", + "pattern": "\\{^env\\:\\/\\/.*\\}$" + }, + "Types.TcpPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "Types.SshPubKey": { + "type": "string", + "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" + }, + "Types.Uri": { + "type": "string", + "pattern": "^(http|https)\\:\\/\\/.+$" + }, + "Types.AwsArn": { + "type": "string", + "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{16})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{16}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "enum": ["tcp", "udp", "icmp", "icmpv6", "-1"] + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" + } + } + } + }, + "Types.FuryModuleOverridesIngresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/ansible b/automated-tests/integration/validation-cmd/test-data/dependencies-correct/ansible new file mode 100755 index 000000000..7e6d30dae --- /dev/null +++ b/automated-tests/integration/validation-cmd/test-data/dependencies-correct/ansible @@ -0,0 +1,13 @@ +#!/bin/sh + +cat <&3 + fi + [ "${status}" -eq 0 ] +} + +@test "invalid furyctl yaml" { + info + test_dir="./automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml" + abs_test_dir=${PWD}/${test_dir} + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} + } + run init + + [ "${status}" -eq 1 ] + + if [[ ${output} != *"furyctl.yaml contains validation errors"* ]]; then + echo "${output}" >&3 + fi + [[ "${output}" == *"furyctl.yaml contains validation errors"* ]] +} + +@test "valid furyctl yaml" { + info + test_dir="./automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml" + abs_test_dir=${PWD}/${test_dir} + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && ${furyctl_bin} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} + } + run init + + [ "${status}" -eq 0 ] +} + +@test "dependencies missing" { + info + test_dir="./automated-tests/integration/validation-cmd/test-data/dependencies-missing" + abs_test_dir=${PWD}/${test_dir} + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && \ + ${furyctl_bin} -d --debug \ + validate dependencies \ + --config ${abs_test_dir}/furyctl.yaml \ + --distro-location ${abs_test_dir} \ + --bin-path=${abs_test_dir} + } + run init + + [ "${status}" -eq 1 ] + + if [[ ${output} != *"ansible: no such file or directory"* ]] || \ + [[ ${output} != *"terraform: no such file or directory"* ]] || \ + [[ ${output} != *"kubectl: no such file or directory"* ]] || \ + [[ ${output} != *"kustomize: no such file or directory"* ]] || \ + [[ ${output} != *"furyagent: no such file or directory"* ]]; then + echo "${output}" >&3 + fi + + [[ "${output}" == *"error while validating system dependencies"* ]] +} + +@test "wrong dependencies installed" { + info + test_dir="./automated-tests/integration/validation-cmd/test-data/dependencies-wrong" + abs_test_dir=${PWD}/${test_dir} + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + cd ${test_dir} && \ + ${furyctl_bin} -d --debug \ + validate dependencies \ + --config ${abs_test_dir}/furyctl.yaml \ + --distro-location ${abs_test_dir} \ + --bin-path=${abs_test_dir} + } + run init + + [ "${status}" -eq 1 ] + + if [[ ${output} != *"ansible version on system"* ]] || \ + [[ ${output} != *"terraform version on system"* ]] || \ + [[ ${output} != *"kubectl version on system"* ]] || \ + [[ ${output} != *"kustomize version on system"* ]] || \ + [[ ${output} != *"furyagent version on system"* ]]; then + echo "${output}" >&3 + fi + + [[ "${output}" == *"error while validating system dependencies"* ]] +} + +@test "correct dependencies installed" { + info + test_dir="./automated-tests/integration/validation-cmd/test-data/dependencies-correct" + abs_test_dir=${PWD}/${test_dir} + furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl + init(){ + export AWS_ACCESS_KEY_ID=foo + export AWS_SECRET_ACCESS_KEY=bar + export AWS_DEFAULT_REGION=baz + + cd ${test_dir} && \ + ${furyctl_bin} -d --debug \ + validate dependencies \ + --config ${abs_test_dir}/furyctl.yaml \ + --distro-location ${abs_test_dir} \ + --bin-path=${abs_test_dir} + } + run init + + [ "${status}" -eq 0 ] + + if [[ ${output} != *"ansible version on system"* ]] && \ + [[ ${output} != *"terraform version on system"* ]] && \ + [[ ${output} != *"kubectl version on system"* ]] && \ + [[ ${output} != *"kustomize version on system"* ]] && \ + [[ ${output} != *"furyagent version on system"* ]]; then + echo "${output}" >&3 + fi + + [[ "${output}" == *"All dependencies are satisfied"* ]] +} diff --git a/cmd/validate.go b/cmd/validate.go index 50ef6d30c..453b6eee3 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -19,4 +19,5 @@ var ( func init() { validateCmd.AddCommand(validate.NewConfigCmd(version)) + validateCmd.AddCommand(validate.NewDependenciesCmd(version)) } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index ff72b1193..934a761a9 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -19,8 +19,6 @@ import ( "github.com/sighupio/furyctl/internal/yaml" ) -const defaultBaseUrl = "https://git@github.com/sighupio/fury-distribution?ref=%s" - var errHasValidationErrors = fmt.Errorf("furyctl.yaml contains validation errors") func NewConfigCmd(version string) *cobra.Command { @@ -28,9 +26,9 @@ func NewConfigCmd(version string) *cobra.Command { Use: "config", Short: "Validate furyctl.yaml file", RunE: func(cmd *cobra.Command, _ []string) error { - debug := cmd.Flag("debug").Value.String() == "true" - furyctlPath := cmd.Flag("config").Value.String() - distroLocation := cmd.Flag("distro-location").Value.String() + debug := flag[bool](cmd, "debug").(bool) + furyctlPath := flag[string](cmd, "config").(string) + distroLocation := flag[string](cmd, "distro-location").(string) minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) if err != nil { @@ -40,7 +38,7 @@ func NewConfigCmd(version string) *cobra.Command { furyctlConfVersion := minimalConf.Spec.DistributionVersion if version != "dev" { - furyctlBinVersion, err := semver.NewVersion(version) + furyctlBinVersion, err := semver.NewVersion(fmt.Sprintf("v%s", version)) if err != nil { return err } @@ -57,7 +55,7 @@ func NewConfigCmd(version string) *cobra.Command { } if distroLocation == "" { - distroLocation = fmt.Sprintf(defaultBaseUrl, furyctlConfVersion.String()) + distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfVersion.String()) } repoPath, err := downloadDirectory(distroLocation) diff --git a/cmd/validate/config_test.go b/cmd/validate/config_test.go new file mode 100644 index 000000000..7dd7190a3 --- /dev/null +++ b/cmd/validate/config_test.go @@ -0,0 +1,346 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package validate_test + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/sighupio/furyctl/cmd/validate" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/yaml" + "os" + "path/filepath" + "testing" +) + +var ( + furyConfig = map[string]interface{}{ + "apiVersion": "kfd.sighup.io/v1alpha2", + "kind": "EKSCluster", + "spec": map[string]interface{}{ + "distributionVersion": "v1.24.7", + "distribution": map[string]interface{}{}, + }, + } + + kfdConfig = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{}, + } + + defaults = map[string]interface{}{ + "data": map[string]interface{}{ + "modules": map[string]interface{}{ + "ingress": map[string]interface{}{ + "test": "test", + }, + }, + }, + } + + failingDefaults = map[string]interface{}{ + "data": map[string]interface{}{ + "modules": map[string]interface{}{ + "ingress": map[string]interface{}{ + "test": "test", + "unexpected": "test", + }, + }, + }, + } + + schema = map[string]interface{}{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/1.23.2/ekscluster-kfd-v1alpha2.json", + "type": "object", + "additionalProperties": false, + "properties": map[string]interface{}{ + "apiVersion": map[string]interface{}{ + "type": "string", + }, + "kind": map[string]interface{}{ + "type": "string", + }, + "spec": map[string]interface{}{ + "type": "object", + "additionalProperties": false, + "properties": map[string]interface{}{ + "distributionVersion": map[string]interface{}{ + "type": "string", + }, + "distribution": map[string]interface{}{ + "type": "object", + "additionalProperties": false, + "properties": map[string]interface{}{ + "modules": map[string]interface{}{ + "type": "object", + "additionalProperties": false, + "properties": map[string]interface{}{ + "ingress": map[string]interface{}{ + "type": "object", + "additionalProperties": false, + "properties": map[string]interface{}{ + "test": map[string]interface{}{ + "type": "string", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +) + +func TestNewConfigCmd_ConfigNotFound(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + expectedErr := fmt.Errorf("open %s: no such file or directory", configFilePath) + + b := bytes.NewBufferString("") + valCmd := validate.NewConfigCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath} + valCmd.SetArgs(args) + + err = valCmd.Execute() + if err != nil && err.Error() != expectedErr.Error() { + t.Errorf("Expected error %v, got %v", expectedErr, err) + return + } +} + +func TestNewConfigCmd_WrongDistroLocation(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + expectedErr := fmt.Errorf("error downloading folder 'wrong-location': downloading options exausted") + + b := bytes.NewBufferString("") + valCmd := validate.NewConfigCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath, "--distro-location", "wrong-location"} + valCmd.SetArgs(args) + + err = valCmd.Execute() + if err != nil && err.Error() != expectedErr.Error() { + t.Errorf("Expected error %v, got %v", expectedErr, err) + return + } +} + +func TestNewConfigCmd_SuccessValidation(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(kfdConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") + + defaultsYaml, err := yaml.MarshalV2(defaults) + if err != nil { + t.Fatalf("error marshaling furyctl defaults: %v", err) + } + + if err = os.WriteFile(defaultsFilePath, defaultsYaml, os.ModePerm); err != nil { + t.Fatalf("error writing furyctl defaults: %v", err) + } + + schemasPath := filepath.Join(tmpDir, "schemas") + + err = os.Mkdir(schemasPath, os.ModePerm) + if err != nil { + t.Fatalf("error creating schemas dir: %v", err) + } + + schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") + + schemaJson, err := json.Marshal(schema) + if err != nil { + t.Fatalf("error marshaling schema: %v", err) + } + + if err = os.WriteFile(schemaFilePath, schemaJson, os.ModePerm); err != nil { + t.Fatalf("error writing schema json: %v", err) + } + + b := bytes.NewBufferString("") + valCmd := validate.NewConfigCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath, "--distro-location", tmpDir} + valCmd.SetArgs(args) + + err = valCmd.Execute() + if err != nil { + t.Errorf("Expected no error, got %v", err) + return + } +} + +func TestNewConfigCmd_FailValidation(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(kfdConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") + + defaultsYaml, err := yaml.MarshalV2(failingDefaults) + if err != nil { + t.Fatalf("error marshaling furyctl defaults: %v", err) + } + + if err = os.WriteFile(defaultsFilePath, defaultsYaml, os.ModePerm); err != nil { + t.Fatalf("error writing furyctl defaults: %v", err) + } + + schemasPath := filepath.Join(tmpDir, "schemas") + + err = os.Mkdir(schemasPath, os.ModePerm) + if err != nil { + t.Fatalf("error creating schemas dir: %v", err) + } + + schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") + + schemaJson, err := json.Marshal(schema) + if err != nil { + t.Fatalf("error marshaling schema: %v", err) + } + + if err = os.WriteFile(schemaFilePath, schemaJson, os.ModePerm); err != nil { + t.Fatalf("error writing schema json: %v", err) + } + + b := bytes.NewBufferString("") + valCmd := validate.NewConfigCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath, "--distro-location", tmpDir} + valCmd.SetArgs(args) + + expectedError := "furyctl.yaml contains validation errors" + + err = valCmd.Execute() + if err != nil && err.Error() != expectedError { + t.Errorf("Expected error %q, got %v", expectedError, err) + return + } +} diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go new file mode 100644 index 000000000..c8ecfdced --- /dev/null +++ b/cmd/validate/dependencies.go @@ -0,0 +1,382 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package validate + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/yaml" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + ErrSystemDepsValidation = errors.New("error while validating system dependencies") + ErrEnvironmentDepsValidation = errors.New("error while validating environment dependencies") + ErrEmptyToolVersion = errors.New("empty tool version") +) + +var execCommand = exec.Command + +func NewDependenciesCmd(version string) *cobra.Command { + cmd := &cobra.Command{ + Use: "dependencies", + Short: "Validate furyctl.yaml file", + RunE: func(cmd *cobra.Command, _ []string) error { + debug := flag[bool](cmd, "debug").(bool) + binPath := flag[string](cmd, "bin-path").(string) + furyctlPath := flag[string](cmd, "config").(string) + distroLocation := flag[string](cmd, "distro-location").(string) + + minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) + if err != nil { + return err + } + + furyctlConfVersion := minimalConf.Spec.DistributionVersion + + if version != "dev" { + furyctlBinVersion, err := semver.NewVersion(fmt.Sprintf("v%s", version)) + if err != nil { + return err + } + + sameMinors := semver.SameMinor(furyctlConfVersion, furyctlBinVersion) + + if !sameMinors { + logrus.Warnf( + "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", + furyctlBinVersion, + furyctlConfVersion, + ) + } + } + + if distroLocation == "" { + distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfVersion.String()) + } + + repoPath, err := downloadDirectory(distroLocation) + if err != nil { + return err + } + if !debug { + defer cleanupTempDir(filepath.Base(repoPath)) + } + + kfdPath := filepath.Join(repoPath, "kfd.yaml") + kfdManifest, err := yaml.FromFileV3[distribution.Manifest](kfdPath) + if err != nil { + return err + } + + if !semver.SamePatch(furyctlConfVersion, kfdManifest.Version) { + return fmt.Errorf( + "versions mismatch: furyctl.yaml has %s, but furyctl has %s", + furyctlConfVersion.String(), + kfdManifest.Version.String(), + ) + } + + logrus.Debugln("Checking system dependencies") + if err := validateSystemDependencies(kfdManifest, binPath); err != nil { + return err + } + + logrus.Debugln("Checking environment dependencies") + if err := validateEnvDependencies(minimalConf.Kind); err != nil { + return err + } + + fmt.Println("All dependencies are satisfied") + + return nil + }, + } + + cmd.Flags().StringP( + "bin-path", + "b", + "", + "Path to the bin folder where all dependencies are installed", + ) + + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Base URL used to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: https://git@github.com/sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + return cmd +} + +func validateEnvDependencies(kind distribution.Kind) error { + errs := make([]error, 0) + + if kind.Equals(distribution.EKSCluster) { + if os.Getenv("AWS_ACCESS_KEY_ID") == "" { + missingAccessKeyIdErr := fmt.Errorf("missing environment variable with key: AWS_ACCESS_KEY_ID") + logrus.Error(missingAccessKeyIdErr) + errs = append(errs, missingAccessKeyIdErr) + } + + if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { + missingSecretAccessKeyErr := fmt.Errorf("missing environment variable with key: AWS_SECRET_ACCESS_KEY") + logrus.Error(missingSecretAccessKeyErr) + errs = append(errs, missingSecretAccessKeyErr) + } + + if os.Getenv("AWS_DEFAULT_REGION") == "" { + missingDefaultRegionErr := fmt.Errorf("missing environment variable with key: AWS_DEFAULT_REGION") + logrus.Error(missingDefaultRegionErr) + errs = append(errs, missingDefaultRegionErr) + } + } + + if len(errs) > 0 { + return ErrEnvironmentDepsValidation + } + + return nil +} + +func validateSystemDependencies(kfdManifest distribution.Manifest, binPath string) error { + errs := make([]error, 0) + + if err := checkAnsibleVersion(kfdManifest.Tools.Ansible, binPath); err != nil { + logrus.Error(err) + errs = append(errs, err) + } + + if err := checkTerraformVersion(kfdManifest.Tools.Terraform, binPath); err != nil { + logrus.Error(err) + errs = append(errs, err) + } + + if err := checkKubectlVersion(kfdManifest.Tools.Kubectl, binPath); err != nil { + logrus.Error(err) + errs = append(errs, err) + } + + if err := checkKustomizeVersion(kfdManifest.Tools.Kustomize, binPath); err != nil { + logrus.Error(err) + errs = append(errs, err) + } + + if kfdManifest.Tools.Furyagent != "" { + if err := checkFuryagentVersion(kfdManifest.Tools.Furyagent, binPath); err != nil { + logrus.Error(err) + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return ErrSystemDepsValidation + } + + return nil +} + +func checkAnsibleVersion(wantVer string, binPath string) error { + if wantVer == "" { + return fmt.Errorf("ansible: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "ansible") + out, err := execCommand(path, "--version").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("ansible \\[.*]") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get ansible version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, " ") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get ansible version from system") + } + + systemAnsibleVersion := strings.TrimRight(versionStringTokens[len(versionStringTokens)-1], "]") + + if systemAnsibleVersion != wantVer { + return fmt.Errorf("ansible version on system: %s, required version: %s", systemAnsibleVersion, wantVer) + } + + return nil +} + +func checkTerraformVersion(wantVer string, binPath string) error { + if wantVer == "" { + return fmt.Errorf("terraform: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "terraform") + out, err := execCommand(path, "--version").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("Terraform .*") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get terraform version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, " ") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get terraform version from system") + } + + systemTerraformVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") + + if systemTerraformVersion != wantVer { + return fmt.Errorf("terraform version on system: %s, required version: %s", systemTerraformVersion, wantVer) + } + + return nil +} + +func checkKubectlVersion(wantVer string, binPath string) error { + if wantVer == "" { + return fmt.Errorf("kubectl: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "kubectl") + out, err := execCommand(path, "version", "--client").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("GitVersion:\"([^\"]*)\"") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get kubectl version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, ":") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get kubectl version from system") + } + + systemKubectlVersion := strings.TrimRight( + strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "\"v"), + "\"", + ) + + if systemKubectlVersion != wantVer { + return fmt.Errorf("kubectl version on system: %s, required version: %s", systemKubectlVersion, wantVer) + } + + return nil +} + +func checkKustomizeVersion(wantVer string, binPath string) error { + if wantVer == "" { + return fmt.Errorf("kustomize: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "kustomize") + out, err := execCommand(path, "version", "--short").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("kustomize/v(\\S*)") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get kustomize version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, "/") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get kustomize version from system") + } + + systemKustomizeVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") + + if systemKustomizeVersion != wantVer { + return fmt.Errorf("kustomize version on system: %s, required version: %s", systemKustomizeVersion, wantVer) + } + + return nil +} + +func checkFuryagentVersion(wantVer string, binPath string) error { + if wantVer == "" { + return fmt.Errorf("furyagent: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "furyagent") + out, err := execCommand(path, "version").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("version (\\S*)") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get furyagent version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, " ") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get furyagent version from system") + } + + systemFuryagentVersion := versionStringTokens[len(versionStringTokens)-1] + + if systemFuryagentVersion != wantVer { + return fmt.Errorf("furyagent version on system: %s, required version: %s", systemFuryagentVersion, wantVer) + } + + return nil +} diff --git a/cmd/validate/dependencies_test.go b/cmd/validate/dependencies_test.go new file mode 100644 index 000000000..3aaea5ea9 --- /dev/null +++ b/cmd/validate/dependencies_test.go @@ -0,0 +1,644 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package validate + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/yaml" +) + +var ( + furyConfig = map[string]interface{}{ + "apiVersion": "kfd.sighup.io/v1alpha2", + "kind": "EKSCluster", + "spec": map[string]interface{}{ + "distributionVersion": "v1.24.7", + "distribution": map[string]interface{}{}, + }, + } + + kfdConfigCorrect = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{ + Ansible: "2.11.2", + Furyagent: "0.0.1", + Kubectl: "1.21.1", + Kustomize: "3.9.4", + Terraform: "0.15.4", + }, + } + + kfdConfigWrong = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{ + Ansible: "2.10.0", + Furyagent: "0.0.2", + Kubectl: "1.21.4", + Kustomize: "3.9.8", + Terraform: "0.15.9", + }, + } +) + +func TestNewDependenciesCmd_FailSysDepsValidation(t *testing.T) { + execCommand = fakeExecCommand + + defer func() { + execCommand = exec.Command + }() + + tmpDir, err := os.MkdirTemp("", "furyctl-deps-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(kfdConfigWrong) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + b := bytes.NewBufferString("") + valCmd := NewDependenciesCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath, "--distro-location", tmpDir} + valCmd.SetArgs(args) + + err = valCmd.Execute() + if err != ErrSystemDepsValidation { + t.Fatalf("want: %v, got: %v", ErrSystemDepsValidation, err) + } +} + +func TestNewDependenciesCmd_FailEnvVarsValidation(t *testing.T) { + execCommand = fakeExecCommand + + defer func() { + execCommand = exec.Command + }() + + tmpDir, err := os.MkdirTemp("", "furyctl-deps-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(kfdConfigCorrect) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + b := bytes.NewBufferString("") + valCmd := NewDependenciesCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath, "--distro-location", tmpDir} + valCmd.SetArgs(args) + + t.Setenv("AWS_ACCESS_KEY_ID", "") + t.Setenv("AWS_SECRET_ACCESS_KEY", "") + t.Setenv("AWS_DEFAULT_REGION", "") + + err = valCmd.Execute() + if err != ErrEnvironmentDepsValidation { + t.Fatalf("want: %v, got: %v", ErrEnvironmentDepsValidation, err) + } +} + +func TestNewDependenciesCmd_SuccessValidation(t *testing.T) { + execCommand = fakeExecCommand + + defer func() { + execCommand = exec.Command + }() + + tmpDir, err := os.MkdirTemp("", "furyctl-deps-validation-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(kfdConfigCorrect) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + b := bytes.NewBufferString("") + valCmd := NewDependenciesCmd("dev") + + valCmd.SetOut(b) + + args := []string{"--config", configFilePath, "--distro-location", tmpDir} + valCmd.SetArgs(args) + + t.Setenv("AWS_ACCESS_KEY_ID", "test") + t.Setenv("AWS_SECRET_ACCESS_KEY", "test") + t.Setenv("AWS_DEFAULT_REGION", "test") + + err = valCmd.Execute() + if err != nil { + t.Fatalf("want: %v, got: %v", nil, err) + } +} + +func TestValidateEnvDependencies(t *testing.T) { + tests := []struct { + name string + kind distribution.Kind + setup func() + wantErr error + }{ + { + name: "test with correct dependencies: eks", + kind: "EKSCluster", + setup: func() { + t.Setenv("AWS_ACCESS_KEY_ID", "test") + t.Setenv("AWS_SECRET_ACCESS_KEY", "test") + t.Setenv("AWS_DEFAULT_REGION", "test") + }, + wantErr: nil, + }, + { + name: "test with incorrect dependencies: eks", + kind: "EKSCluster", + setup: func() { + t.Setenv("AWS_ACCESS_KEY_ID", "") + t.Setenv("AWS_SECRET_ACCESS_KEY", "") + t.Setenv("AWS_DEFAULT_REGION", "") + }, + wantErr: ErrEnvironmentDepsValidation, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup() + err := validateEnvDependencies(tt.kind) + if err != nil { + if tt.wantErr == nil { + t.Errorf("validateEnvDependencies() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("validateEnvDependencies() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("validateEnvDependencies() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestValidateSystemDependencies(t *testing.T) { + execCommand = fakeExecCommand + defer func() { execCommand = exec.Command }() + + tests := []struct { + name string + manifest distribution.Manifest + wantErr error + }{ + { + name: "test with correct dependencies", + manifest: kfdConfigCorrect, + wantErr: nil, + }, + { + name: "test with incorrect dependencies", + manifest: kfdConfigWrong, + wantErr: ErrSystemDepsValidation, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateSystemDependencies(tt.manifest, "") + if err != nil { + if tt.wantErr == nil { + t.Errorf("validateSystemDependencies() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("validateSystemDependencies() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("validateSystemDependencies() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestCheckAnsibleVersion(t *testing.T) { + execCommand = fakeExecCommand + defer func() { execCommand = exec.Command }() + + tests := []struct { + name string + version string + want string + wantErr error + }{ + { + name: "test with correct ansible version", + version: "2.11.2", + want: "", + wantErr: nil, + }, + { + name: "test with incorrect ansible version", + version: "2.10.6", + want: "", + wantErr: fmt.Errorf("ansible version on system: 2.11.2, required version: 2.10.6"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkAnsibleVersion(tt.version, "") + if err != nil { + if tt.wantErr == nil { + t.Errorf("checkAnsibleVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("checkAnsibleVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("checkAnsibleVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestCheckTerraformVersion(t *testing.T) { + execCommand = fakeExecCommand + defer func() { execCommand = exec.Command }() + + tests := []struct { + name string + version string + want string + wantErr error + }{ + { + name: "test with correct terraform version", + version: "0.15.4", + want: "", + wantErr: nil, + }, + { + name: "test with incorrect terraform version", + version: "0.14.6", + want: "", + wantErr: fmt.Errorf("terraform version on system: 0.15.4, required version: 0.14.6"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkTerraformVersion(tt.version, "") + if err != nil { + if tt.wantErr == nil { + t.Errorf("checkTerraformVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("checkTerraformVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("checkTerraformVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestCheckKubectlVersion(t *testing.T) { + execCommand = fakeExecCommand + defer func() { execCommand = exec.Command }() + + tests := []struct { + name string + version string + want string + wantErr error + }{ + { + name: "test with correct kubectl version", + version: "1.21.1", + want: "", + wantErr: nil, + }, + { + name: "test with incorrect kubectl version", + version: "1.21.2", + want: "", + wantErr: fmt.Errorf("kubectl version on system: 1.21.1, required version: 1.21.2"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkKubectlVersion(tt.version, "") + if err != nil { + if tt.wantErr == nil { + t.Errorf("checkKubectlVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("checkKubectlVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("checkKubectlVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestCheckKustomizeVersion(t *testing.T) { + execCommand = fakeExecCommand + defer func() { execCommand = exec.Command }() + + tests := []struct { + name string + version string + want string + wantErr error + }{ + { + name: "test with correct kustomize version", + version: "3.9.4", + want: "", + wantErr: nil, + }, + { + name: "test with incorrect kustomize version", + version: "3.9.3", + want: "", + wantErr: fmt.Errorf("kustomize version on system: 3.9.4, required version: 3.9.3"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkKustomizeVersion(tt.version, "") + if err != nil { + if tt.wantErr == nil { + t.Errorf("checkKustomizeVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("checkKustomizeVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("checkKustomizeVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestCheckFuryagentVersion(t *testing.T) { + execCommand = fakeExecCommand + defer func() { execCommand = exec.Command }() + + tests := []struct { + name string + version string + want string + wantErr error + }{ + { + name: "test with correct furyagent version", + version: "0.0.1", + want: "", + wantErr: nil, + }, + { + name: "test with incorrect furyagent version", + version: "0.0.2", + want: "", + wantErr: fmt.Errorf("furyagent version on system: 0.0.1, required version: 0.0.2"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkFuryagentVersion(tt.version, "") + if err != nil { + if tt.wantErr == nil { + t.Errorf("checkFuryagentVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err.Error() != tt.wantErr.Error() { + t.Errorf("checkFuryagentVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + + if err == nil && tt.wantErr != nil { + t.Errorf("checkFuryagentVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func fakeExecCommand(command string, args ...string) *exec.Cmd { + cs := []string{"-test.run=TestHelperProcess", "--", command} + cs = append(cs, args...) + cmd := exec.Command(os.Args[0], cs...) + return cmd +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, _ := args[3], args[4:] + + switch cmd { + case "ansible": + fmt.Fprintf(os.Stdout, "ansible [core 2.11.2]\n "+ + "config file = None\n "+ + "configured module search path = ['', '']\n"+ + "ansible python module location = ./ansible\n"+ + "ansible collection location = ./ansible/collections\n"+ + "executable location = ./bin/ansible\n "+ + "python version = 3.9.14\n"+ + "jinja version = 3.1.2\n"+ + "libyaml = True\n") + case "terraform": + fmt.Fprintf(os.Stdout, "Terraform v0.15.4\non darwin_amd64") + case "kubectl": + fmt.Fprintf(os.Stdout, "Client Version: version.Info{Major:\"1\", "+ + "Minor:\"21\", GitVersion:\"v1.21.1\", GitCommit:\"xxxxx\", "+ + "GitTreeState:\"clean\", BuildDate:\"2021-05-12T14:00:00Z\", "+ + "GoVersion:\"go1.16.4\", Compiler:\"gc\", Platform:\"darwin/amd64\"}\n") + case "kustomize": + fmt.Fprintf(os.Stdout, "Version: {kustomize/v3.9.4 GitCommit:xxxxxxx"+ + "BuildDate:2021-05-12T14:00:00Z GoOs:darwin GoArch:amd64}") + case "furyagent": + fmt.Fprintf(os.Stdout, "furyagent version 0.0.1") + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} diff --git a/cmd/validate/util.go b/cmd/validate/util.go index 827e9af3b..682fd5f65 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -9,14 +9,18 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "github.com/hashicorp/go-getter" "github.com/sirupsen/logrus" + "github.com/spf13/cobra" "github.com/sighupio/furyctl/internal/distribution" ) +const DefaultBaseUrl = "https://git@github.com/sighupio/fury-distribution?ref=%s" + var ( downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} @@ -33,6 +37,34 @@ var ( ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) +func flag[T bool | int | string](cmd *cobra.Command, name string) any { + var f T + + if cmd == nil { + return f + } + + if cmd.Flag(name) == nil { + return f + } + + v := cmd.Flag(name).Value.String() + + if v == "true" { + return true + } + + if v == "false" { + return false + } + + if vv, err := strconv.Atoi(v); err == nil { + return vv + } + + return v +} + func getSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, error) { avp := strings.Split(conf.ApiVersion, "/") @@ -47,7 +79,7 @@ func getSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, er return "", fmt.Errorf("kind is empty") } - filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind), ns, ver) + filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind.String()), ns, ver) return filepath.Join(basePath, "schemas", filename), nil } diff --git a/go.mod b/go.mod index 42e910763..c5367c124 100644 --- a/go.mod +++ b/go.mod @@ -21,14 +21,14 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.8.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.0 golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 golang.org/x/term v0.5.0 // indirect golang.org/x/tools v0.5.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) -require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +require gopkg.in/yaml.v3 v3.0.1 require ( cloud.google.com/go v0.81.0 // indirect @@ -40,8 +40,8 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.10.0 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/gobuffalo/logger v1.0.6 // indirect - github.com/gobuffalo/packd v1.0.1 // indirect + github.com/gobuffalo/logger v1.0.7 // indirect + github.com/gobuffalo/packd v1.0.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect @@ -55,7 +55,7 @@ require ( github.com/hashicorp/terraform-json v0.10.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/jstemmer/go-junit-report v0.9.1 // indirect github.com/karrick/godirwalk v1.17.0 // indirect @@ -87,7 +87,8 @@ require ( golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect - golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect + golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect + golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect google.golang.org/api v0.44.0 // indirect diff --git a/go.sum b/go.sum index bc60eb303..e1277df4b 100644 --- a/go.sum +++ b/go.sum @@ -257,7 +257,7 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -545,7 +545,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= +golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -598,8 +599,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= -golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= +golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= diff --git a/internal/distribution/model.go b/internal/distribution/model.go index 686542f10..e67ed2fea 100644 --- a/internal/distribution/model.go +++ b/internal/distribution/model.go @@ -6,9 +6,19 @@ package distribution import "github.com/sighupio/furyctl/internal/semver" +type Kind string + +func (k Kind) String() string { + return string(k) +} + +func (k Kind) Equals(kk Kind) bool { + return k == kk +} + type FuryctlConfig struct { ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` + Kind Kind `yaml:"kind"` Spec struct { DistributionVersion semver.Version `yaml:"distributionVersion"` } `yaml:"spec"` diff --git a/internal/distribution/resources.go b/internal/distribution/resources.go new file mode 100644 index 000000000..e69176e61 --- /dev/null +++ b/internal/distribution/resources.go @@ -0,0 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +const EKSCluster Kind = "EKSCluster" From 944a430942e44673857992b079294d49b5fcc0a0 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Tue, 20 Sep 2022 19:32:59 +0200 Subject: [PATCH 015/383] feat(template): load _helpers.tpl together with the template load a `_helpers.tpl` file if exists and load it together with the temlpate to be rendered, allowing to define helper functions in the template logic. This allows for example to have common logic shared between template files, for example, e function to get the ingress class. --- internal/template/generator.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/template/generator.go b/internal/template/generator.go index 579b7a784..3420f678a 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -6,7 +6,9 @@ package template import ( "bytes" + "errors" "fmt" + "os" "path/filepath" "strings" "text/template" @@ -40,8 +42,18 @@ func NewGenerator( } func (g *generator) ProcessTemplate() *template.Template { - return template.Must( - template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source)) + const helpersPath = "source/_helpers.tpl" + if _, err := os.Stat(helpersPath); err == nil { + return template.Must( + template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source, helpersPath)) + } else if errors.Is(err, os.ErrNotExist) { + logrus.Warnf("template helpers file '%s' not found\n", helpersPath) + return template.Must( + template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source)) + } else { + logrus.Fatalf("got unexpected error when checking if helper file exists at path %s: %s\n", helpersPath, err) + panic(err) + } } func (g *generator) GetMissingKeys(tpl *template.Template) []string { From fa31724cf930a6fabee511a35a682b22dd8fe8e8 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Wed, 21 Sep 2022 11:38:53 +0200 Subject: [PATCH 016/383] fix(template): delete panic() calling --- internal/template/generator.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/template/generator.go b/internal/template/generator.go index 3420f678a..bbba6a456 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -52,7 +52,6 @@ func (g *generator) ProcessTemplate() *template.Template { template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source)) } else { logrus.Fatalf("got unexpected error when checking if helper file exists at path %s: %s\n", helpersPath, err) - panic(err) } } From 3f5b9445f3c8abcb2d773e3b9ab5e060b7f1c729 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 09:51:39 +0200 Subject: [PATCH 017/383] fix: replaced a bit of panics with error returns in template.ProcessTemplate --- internal/template/generator.go | 26 +++++++++++++++++--------- internal/template/model.go | 5 ++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/template/generator.go b/internal/template/generator.go index bbba6a456..58ab08a93 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -17,6 +17,10 @@ import ( "github.com/sirupsen/logrus" ) +var ( + ErrProcessTemplate = errors.New("error processing template") +) + type generator struct { source string target string @@ -41,18 +45,22 @@ func NewGenerator( } } -func (g *generator) ProcessTemplate() *template.Template { +func (g *generator) ProcessTemplate() (*template.Template, error) { const helpersPath = "source/_helpers.tpl" - if _, err := os.Stat(helpersPath); err == nil { - return template.Must( - template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source, helpersPath)) - } else if errors.Is(err, os.ErrNotExist) { + + _, err := os.Stat(helpersPath) + + if err == nil { + return template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source, helpersPath) + } + + if errors.Is(err, os.ErrNotExist) { logrus.Warnf("template helpers file '%s' not found\n", helpersPath) - return template.Must( - template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source)) - } else { - logrus.Fatalf("got unexpected error when checking if helper file exists at path %s: %s\n", helpersPath, err) + + return template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source) } + + return nil, fmt.Errorf("%w using helper '%s': %v", ErrProcessTemplate, helpersPath, err) } func (g *generator) GetMissingKeys(tpl *template.Template) []string { diff --git a/internal/template/model.go b/internal/template/model.go index 8162765ab..df1fabd26 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -164,7 +164,10 @@ func (tm *Model) applyTemplates( } if strings.HasSuffix(info.Name(), tm.Suffix) { - tmpl := gen.ProcessTemplate() + tmpl, err := gen.ProcessTemplate() + if err != nil { + return err + } if tmpl == nil { return fmt.Errorf("no template found for %s", relSource) From 3516a5a12a81f4927e096ba32e15da237b9171bd Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:20:33 +0200 Subject: [PATCH 018/383] chore: refactor commands structure, remove old ones that will be replaced --- cmd/bootstrap.go | 227 ----------- cmd/cluster.go | 228 ----------- cmd/completion.go | 88 ++-- cmd/config.go | 189 --------- cmd/download.go | 376 ------------------ cmd/dump.go | 22 + cmd/{ => dump}/template.go | 27 +- cmd/init.go | 53 --- cmd/root.go | 126 +++--- cmd/selfprovision.go | 68 ---- cmd/validate.go | 8 +- cmd/validate/config.go | 7 +- cmd/validate/dependencies.go | 9 +- cmd/validate/util.go | 30 -- cmd/vendor.go | 74 ---- cmd/version.go | 22 +- internal/cobrax/flags.go | 35 ++ internal/configuration/assets/eks-cluster.yml | 6 +- internal/configuration/config_test.go | 15 +- internal/configuration/templates.go | 6 +- main.go | 24 +- 21 files changed, 229 insertions(+), 1411 deletions(-) delete mode 100644 cmd/bootstrap.go delete mode 100644 cmd/cluster.go delete mode 100644 cmd/config.go delete mode 100644 cmd/download.go create mode 100644 cmd/dump.go rename cmd/{ => dump}/template.go (90%) delete mode 100644 cmd/init.go delete mode 100644 cmd/selfprovision.go delete mode 100644 cmd/vendor.go create mode 100644 internal/cobrax/flags.go diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go deleted file mode 100644 index 738bdb5f0..000000000 --- a/cmd/bootstrap.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "bufio" - "errors" - "fmt" - "os" - "os/signal" - "strings" - "syscall" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/sighupio/furyctl/internal/bootstrap" - "github.com/sighupio/furyctl/internal/configuration" - "github.com/sighupio/furyctl/internal/project" - "github.com/sighupio/furyctl/pkg/analytics" - "github.com/sighupio/furyctl/pkg/terraform" -) - -func bPreDestroy(cmd *cobra.Command, args []string) (err error) { - if bForce { - logrus.Warn("Force destroy of the bootstrap project") - return bPre(cmd, args) - } - fmt.Println("\r Are you sure you want to destroy it?\n Write 'yes' to continue") - reader := bufio.NewReader(os.Stdin) - text, err := reader.ReadString('\n') - if err != nil { - os.Exit(2) - } - text = strings.ReplaceAll(text, "\n", "") - if strings.Compare("yes", text) == 0 { - return bPre(cmd, args) - } - return fmt.Errorf("Destroy command aborted") -} - -func bPre(cmd *cobra.Command, args []string) (err error) { - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - handleStopSignal("bootstrap", stop) - - // viper can get the token from an environment variable: FURYCTL_TOKEN - viper.BindPFlag("token", cmd.Flags().Lookup("token")) - if bGitHubToken == "" { // Takes precedence the token from the cli - bGitHubToken = viper.GetString("token") - } - - logrus.Debug("passing pre-flight checks") - err = parseConfig(bConfigFilePath, "Bootstrap") - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - wd, err := os.Getwd() - if err != nil { - return err - } - workingDirFullPath := fmt.Sprintf("%v/%v", wd, bWorkingDir) - logrus.Debug("pre-flight checks ok!") - prj = &project.Project{ - Path: workingDirFullPath, - } - bootstrapOpts := &bootstrap.Options{ - Spin: s, - Project: prj, - ProvisionerConfiguration: cfg, - TerraformOpts: &terraform.Options{ - GitHubToken: bGitHubToken, - WorkingDir: workingDirFullPath, - Debug: debug, - ReconfigureBackend: bReconfigure, - UpgradeDeps: bUpgradeDeps, - }, - } - boot, err = bootstrap.New(bootstrapOpts) - if err != nil { - logrus.Errorf("the bootstrap provisioner can not be initialized: %v", err) - return err - } - return nil -} - -var ( - boot *bootstrap.Bootstrap - - bConfigFilePath string - bWorkingDir string - bGitHubToken string - bTemplateProvisioner string - bReset bool - bReconfigure bool - bDryRun bool - bForce bool - bUpgradeDeps bool - - bootstrapCmd = &cobra.Command{ - Use: "bootstrap", - Short: "Creates the required infrastructure to deploy a battle-tested Kubernetes cluster, mostly network components", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) (err error) { - err = cmd.Help() - if err != nil { - return err - } - return nil - }, - } - bootstrapTemplateCmd = &cobra.Command{ - Use: "template", - Short: "Get a template configuration file for a specific provisioner", - RunE: func(cmd *cobra.Command, args []string) (err error) { - if bTemplateProvisioner == "" { - return errors.New("You must specify a provisioner") - } - tpl, err := configuration.Template("Bootstrap", strings.ToLower(bTemplateProvisioner)) - if err != nil { - return err - } - fmt.Println(tpl) - return nil - }, - } - bootstrapInitCmd = &cobra.Command{ - Use: "init", - Short: "Init a the project. Creates a directory with everything in place to apply the configuration", - PreRunE: bPre, - RunE: func(cmd *cobra.Command, args []string) (err error) { - - err = prj.Check() - if err == nil && !bReset { - return fmt.Errorf("the project %v seems to be already created. Choose another working directory", bWorkingDir) - } - - err = boot.Init(bReset) - if err != nil { - analytics.TrackBootstrapInit(bGitHubToken, false, cfg.Provisioner) - return err - } - analytics.TrackBootstrapInit(bGitHubToken, true, cfg.Provisioner) - return nil - }, - } - bootstrapApplyCmd = &cobra.Command{ - Use: "apply", - Short: "Applies changes to the project. Running for the first time creates everything. Upcoming executions only applies changes.", - PreRunE: bPre, - RunE: func(cmd *cobra.Command, args []string) (err error) { - err = prj.Check() - if err != nil { - return fmt.Errorf("the project %v has to be created. Execute bootstrap init before bootstrap apply. %v", bWorkingDir, err) - } - - err = boot.Update(bDryRun) - if err != nil { - analytics.TrackBootstrapApply(bGitHubToken, false, cfg.Provisioner, bDryRun) - return err - } - analytics.TrackBootstrapApply(bGitHubToken, true, cfg.Provisioner, bDryRun) - return nil - }, - } - bootstrapDestroyCmd = &cobra.Command{ - Use: "destroy", - Short: "ATTENTION: Destroys the project. Ensure you destroy the cluster before destroying the bootstrap project.", - PreRunE: bPreDestroy, - RunE: func(cmd *cobra.Command, args []string) (err error) { - err = prj.Check() - if err != nil { - return fmt.Errorf("the project %v has to be created. Execute bootstrap init before cluster destroy. %v", bWorkingDir, err) - } - - err = boot.Destroy() - if err != nil { - analytics.TrackBootstrapDestroy(bGitHubToken, false, cfg.Provisioner) - return err - } - analytics.TrackBootstrapDestroy(bGitHubToken, true, cfg.Provisioner) - return nil - }, - } -) - -func init() { - bootstrapApplyCmd.PersistentFlags().BoolVar(&bDryRun, "dry-run", false, "Dry run execution") - - bootstrapInitCmd.PersistentFlags().StringVarP(&bConfigFilePath, "config", "c", "bootstrap.yml", "Bootstrap configuration file path") - bootstrapApplyCmd.PersistentFlags().StringVarP(&bConfigFilePath, "config", "c", "bootstrap.yml", "Bootstrap configuration file path") - bootstrapDestroyCmd.PersistentFlags().StringVarP(&bConfigFilePath, "config", "c", "bootstrap.yml", "Bootstrap configuration file path") - - bootstrapInitCmd.PersistentFlags().StringVarP(&bWorkingDir, "workdir", "w", "./bootstrap", "Working directory to create and place all project files. Must not exists.") - bootstrapApplyCmd.PersistentFlags().StringVarP(&bWorkingDir, "workdir", "w", "./bootstrap", "Working directory with all project files") - bootstrapDestroyCmd.PersistentFlags().StringVarP(&bWorkingDir, "workdir", "w", "./bootstrap", "Working directory with all project files") - - bootstrapInitCmd.PersistentFlags().StringVarP(&bGitHubToken, "token", "t", "", "GitHub token to access enterprise repositories. Contact sales@sighup.io") - bootstrapApplyCmd.PersistentFlags().StringVarP(&bGitHubToken, "token", "t", "", "GitHub token to access enterprise repositories. Contact sales@sighup.io") - bootstrapDestroyCmd.PersistentFlags().StringVarP(&bGitHubToken, "token", "t", "", "GitHub token to access enterprise repositories. Contact sales@sighup.io") - - bootstrapInitCmd.PersistentFlags().BoolVar(&bReconfigure, "reconfigure", false, "Reconfigure the backend, ignoring any saved configuration") - bootstrapApplyCmd.PersistentFlags().BoolVar(&bReconfigure, "reconfigure", false, "Reconfigure the backend, ignoring any saved configuration") - bootstrapDestroyCmd.PersistentFlags().BoolVar(&bReconfigure, "reconfigure", false, "Reconfigure the backend, ignoring any saved configuration") - - bootstrapApplyCmd.PersistentFlags().BoolVar(&bUpgradeDeps, "upgrade-deps", false, "Ignore previously-downloaded objects and install the latest version allowed within configured constraints") - - bootstrapInitCmd.PersistentFlags().BoolVar(&bReset, "reset", false, "Forces the re-initialization of the project. It deletes the content of the workdir recreating everything") - - bootstrapDestroyCmd.PersistentFlags().BoolVar(&bForce, "force", false, "Forces the destroy of the project. Doesn't ask for confirmation") - - bootstrapTemplateCmd.PersistentFlags().StringVar( - &bTemplateProvisioner, - "provisioner", - "", - "Bootstrap provisioner, valid options are: aws, gcp", - ) - - bootstrapCmd.AddCommand(bootstrapInitCmd) - bootstrapCmd.AddCommand(bootstrapApplyCmd) - bootstrapCmd.AddCommand(bootstrapDestroyCmd) - bootstrapCmd.AddCommand(bootstrapTemplateCmd) -} diff --git a/cmd/cluster.go b/cmd/cluster.go deleted file mode 100644 index 73ca4c521..000000000 --- a/cmd/cluster.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "bufio" - "errors" - "fmt" - "os" - "os/signal" - "strings" - "syscall" - - "github.com/sighupio/furyctl/internal/cluster" - "github.com/sighupio/furyctl/internal/configuration" - "github.com/sighupio/furyctl/internal/project" - "github.com/sighupio/furyctl/pkg/analytics" - "github.com/sighupio/furyctl/pkg/terraform" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func cPreDestroy(cmd *cobra.Command, args []string) (err error) { - if cForce { - logrus.Warn("Force destroy of the cluster project") - return cPre(cmd, args) - } - fmt.Println("\r Are you sure you want to destroy the cluster?\n Write 'yes' to continue") - reader := bufio.NewReader(os.Stdin) - text, err := reader.ReadString('\n') - if err != nil { - os.Exit(2) - } - text = strings.ReplaceAll(text, "\n", "") - if strings.Compare("yes", text) == 0 { - return cPre(cmd, args) - } - return fmt.Errorf("Destroy command aborted") -} - -func cPre(cmd *cobra.Command, args []string) (err error) { - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - handleStopSignal("cluster", stop) - - // viper can get the token from an environment variable: FURYCTL_TOKEN - viper.BindPFlag("token", cmd.Flags().Lookup("token")) - if cGitHubToken == "" { // Takes precedence the token from the cli - cGitHubToken = viper.GetString("token") - } - - logrus.Debug("passing pre-flight checks") - err = parseConfig(cConfigFilePath, "Cluster") - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - wd, err := os.Getwd() - if err != nil { - return err - } - workingDirFullPath := fmt.Sprintf("%v/%v", wd, cWorkingDir) - logrus.Debug("pre-flight checks ok!") - prj = &project.Project{ - Path: workingDirFullPath, - } - clusterOpts := &cluster.Options{ - Spin: s, - Project: prj, - ProvisionerConfiguration: cfg, - TerraformOpts: &terraform.Options{ - GitHubToken: cGitHubToken, - WorkingDir: workingDirFullPath, - Debug: cDryRun, - ReconfigureBackend: cReconfigure, - UpgradeDeps: cUpgradeDeps, - }, - } - clu, err = cluster.New(clusterOpts) - if err != nil { - logrus.Errorf("the cluster provisioner can not be initialized: %v", err) - return err - } - return nil -} - -var ( - clu *cluster.Cluster - - cConfigFilePath string - cWorkingDir string - cGitHubToken string - cTemplateProvisioner string - cDryRun bool - cReset bool - cReconfigure bool - cForce bool - cUpgradeDeps bool - - clusterCmd = &cobra.Command{ - Use: "cluster", - Short: "Creates a battle-tested Kubernetes cluster", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) (err error) { - err = cmd.Help() - if err != nil { - return err - } - return nil - }, - } - clusterTemplateCmd = &cobra.Command{ - Use: "template", - Short: "Get a template configuration file for a specific provisioner", - RunE: func(cmd *cobra.Command, args []string) (err error) { - if cTemplateProvisioner == "" { - return errors.New("You must specify a provisioner") - } - tpl, err := configuration.Template("Cluster", strings.ToLower(cTemplateProvisioner)) - if err != nil { - return err - } - fmt.Println(tpl) - return nil - }, - } - clusterInitCmd = &cobra.Command{ - Use: "init", - Short: "Init the cluster project. Creates a directory with everything in place to apply the configuration", - PreRunE: cPre, - RunE: func(cmd *cobra.Command, args []string) (err error) { - - err = prj.Check() - if err == nil && !cReset { - return fmt.Errorf("the project %v seems to be already created. Choose another working directory", cWorkingDir) - } - - err = clu.Init(cReset) - if err != nil { - analytics.TrackClusterInit(cGitHubToken, false, cfg.Provisioner) - return err - } - analytics.TrackClusterInit(cGitHubToken, true, cfg.Provisioner) - return nil - }, - } - clusterApplyCmd = &cobra.Command{ - Use: "apply", - Short: "Applies changes to the cluster project. Running for the first time creates everything. Upcoming executions only applies changes.", - PreRunE: cPre, - RunE: func(cmd *cobra.Command, args []string) (err error) { - - err = prj.Check() - if err != nil { - return fmt.Errorf("the project %v has to be created. Execute cluster init before cluster apply. %v", cWorkingDir, err) - } - - err = clu.Update(cDryRun) - if err != nil { - analytics.TrackClusterApply(cGitHubToken, false, cfg.Provisioner, cDryRun) - return err - } - analytics.TrackClusterApply(cGitHubToken, true, cfg.Provisioner, cDryRun) - return nil - }, - } - clusterDestroyCmd = &cobra.Command{ - Use: "destroy", - Short: "ATTENTION: Destroys the cluster project", - PreRunE: cPreDestroy, - RunE: func(cmd *cobra.Command, args []string) (err error) { - err = prj.Check() - - if err != nil { - return fmt.Errorf("the project %v has to be created. Execute cluster init before cluster destroy. %v", cWorkingDir, err) - } - - err = clu.Destroy() - if err != nil { - analytics.TrackClusterDestroy(cGitHubToken, false, cfg.Provisioner) - return err - } - analytics.TrackClusterDestroy(cGitHubToken, true, cfg.Provisioner) - return nil - }, - } -) - -func init() { - clusterApplyCmd.PersistentFlags().BoolVar(&cDryRun, "dry-run", false, "Dry run execution") - - clusterInitCmd.PersistentFlags().StringVarP(&cConfigFilePath, "config", "c", "cluster.yml", "Cluster configuration file path") - clusterApplyCmd.PersistentFlags().StringVarP(&cConfigFilePath, "config", "c", "cluster.yml", "Cluster configuration file path") - clusterDestroyCmd.PersistentFlags().StringVarP(&cConfigFilePath, "config", "c", "cluster.yml", "Cluster configuration file path") - - clusterInitCmd.PersistentFlags().StringVarP(&cWorkingDir, "workdir", "w", "./cluster", "Working directory to create and place all project files. Must not exists.") - clusterApplyCmd.PersistentFlags().StringVarP(&cWorkingDir, "workdir", "w", "./cluster", "Working directory with all project files") - clusterDestroyCmd.PersistentFlags().StringVarP(&cWorkingDir, "workdir", "w", "./cluster", "Working directory with all project files") - - clusterInitCmd.PersistentFlags().StringVarP(&cGitHubToken, "token", "t", "", "GitHub token to access enterprise repositories. Contact sales@sighup.io") - clusterApplyCmd.PersistentFlags().StringVarP(&cGitHubToken, "token", "t", "", "GitHub token to access enterprise repositories. Contact sales@sighup.io") - clusterDestroyCmd.PersistentFlags().StringVarP(&cGitHubToken, "token", "t", "", "GitHub token to access enterprise repositories. Contact sales@sighup.io") - - clusterInitCmd.PersistentFlags().BoolVar(&cReconfigure, "reconfigure", false, "Reconfigure the backend, ignoring any saved configuration") - clusterApplyCmd.PersistentFlags().BoolVar(&cReconfigure, "reconfigure", false, "Reconfigure the backend, ignoring any saved configuration") - clusterDestroyCmd.PersistentFlags().BoolVar(&cReconfigure, "reconfigure", false, "Reconfigure the backend, ignoring any saved configuration") - - clusterInitCmd.PersistentFlags().BoolVar(&cReset, "reset", false, "Forces the re-initialization of the project. It deletes the content of the workdir recreating everything") - - clusterApplyCmd.PersistentFlags().BoolVar(&cUpgradeDeps, "upgrade-deps", false, "Ignore previously-downloaded objects and install the latest version allowed within configured constraints") - - clusterDestroyCmd.PersistentFlags().BoolVar(&cForce, "force", false, "Forces the destroy of the project. Doesn't ask for confirmation") - - clusterTemplateCmd.PersistentFlags().StringVar( - &cTemplateProvisioner, - "provisioner", - "", - "Cluster provisioner, valid options are: eks, gke, vsphere", - ) - - clusterCmd.AddCommand(clusterInitCmd) - clusterCmd.AddCommand(clusterApplyCmd) - clusterCmd.AddCommand(clusterDestroyCmd) - clusterCmd.AddCommand(clusterTemplateCmd) -} diff --git a/cmd/completion.go b/cmd/completion.go index 02dd22c0d..13dd57dae 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -10,61 +10,63 @@ import ( "github.com/spf13/cobra" ) -var completionCmd = &cobra.Command{ - Use: "completion [bash|zsh|fish|powershell]", - Short: "Generate completion script", - Long: `To load furyctl completions: +func NewCompletionCmd() *cobra.Command { + return &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: `To load furyctl completions: -Bash: + Bash: - $ source <(furyctl completion bash) + $ source <(furyctl completion bash) - # To load completions for each session, execute once: - # Linux: - $ furyctl completion bash > /etc/bash_completion.d/furyctl - # macOS: - $ furyctl completion bash > /usr/local/etc/bash_completion.d/furyctl + # To load completions for each session, execute once: + # Linux: + $ furyctl completion bash > /etc/bash_completion.d/furyctl + # macOS: + $ furyctl completion bash > /usr/local/etc/bash_completion.d/furyctl -Zsh: + Zsh: - # If shell completion is not already enabled in your environment, - # you will need to enable it. You can execute the following once: + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: - $ echo "autoload -U compinit; compinit" >> ~/.zshrc + $ echo "autoload -U compinit; compinit" >> ~/.zshrc - # To load completions for each session, execute once: - $ furyctl completion zsh > "${fpath[1]}/_furyctl" + # To load completions for each session, execute once: + $ furyctl completion zsh > "${fpath[1]}/_furyctl" - # You will need to start a new shell for this setup to take effect. + # You will need to start a new shell for this setup to take effect. -fish: + fish: - $ furyctl completion fish | source + $ furyctl completion fish | source - # To load completions for each session, execute once: - $ furyctl completion fish > ~/.config/fish/completions/furyctl.fish + # To load completions for each session, execute once: + $ furyctl completion fish > ~/.config/fish/completions/furyctl.fish -PowerShell: + PowerShell: - PS> furyctl completion powershell | Out-String | Invoke-Expression + PS> furyctl completion powershell | Out-String | Invoke-Expression - # To load completions for every new session, run: - PS> furyctl completion powershell > furyctl.ps1 - # and source this file from your PowerShell profile. -`, - DisableFlagsInUseLine: true, - ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, - Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { - switch args[0] { - case "bash": - cmd.Root().GenBashCompletion(os.Stdout) - case "zsh": - cmd.Root().GenZshCompletion(os.Stdout) - case "fish": - cmd.Root().GenFishCompletion(os.Stdout, true) - case "powershell": - cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) - } - }, + # To load completions for every new session, run: + PS> furyctl completion powershell > furyctl.ps1 + # and source this file from your PowerShell profile. + `, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + } + }, + } } diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index a84c68841..000000000 --- a/cmd/config.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "fmt" - "strings" - - "github.com/sirupsen/logrus" -) - -const ( - configFile = "Furyfile" - defaultVendorFolderName = "vendor" -) - -// Furyconf is responsible for the structure of the Furyfile -type Furyconf struct { - VendorFolderName string `yaml:"vendorFolderName"` - Versions VersionPattern `yaml:"versions"` - Roles []Package `yaml:"roles"` - Modules []Package `yaml:"modules"` - Bases []Package `yaml:"bases"` - Provider ProviderPattern `mapstructure:"provider"` -} - -// ProviderPattern is the abstraction of the following structure: -// provider: -// -// modules: -// aws -// - uri: https://github.com/terraform-aws-modules -// label: official-modules -type ProviderPattern map[string]ProviderKind - -// ProviderKind is the abstraction of the following structure: -// -// modules: -// -// aws -// - uri: https://github.com/terraform-aws-modules -// label: official-modules -type ProviderKind map[string][]RegistrySpec - -// RegistrySpec contains the couple uri/label to identify each tf new repo declared -type RegistrySpec struct { - BaseURI string `mapstructure:"url"` - Label string `mapstructure:"label"` -} - -// VersionPattern Map from glob pattern to version associated (e.g. {"aws/*" : "v1.15.4-1"} -type VersionPattern map[string]string - -// Package is the type to contain the definition of a single package -type Package struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - Url string `yaml:"url"` - Dir string `yaml:"dir"` - Kind string `yaml:"kind"` - ProviderOpt ProviderOptSpec `mapstructure:"provider"` - ProviderKind ProviderKind `mapstructure:"providerKind"` - Registry bool `mapstructure:"registry"` -} - -// ProviderOptSpec is the type that allows to explicit name of cloud provider and referenced label -type ProviderOptSpec struct { - Name string `mapstructure:"name"` - Label string `mapstructure:"label"` -} - -// Validate is used for validation of configuration and initization of default parameters -func (f *Furyconf) Validate() error { - if f.VendorFolderName == "" { - f.VendorFolderName = defaultVendorFolderName - } - return nil -} - -// Parse reads the furyconf structs and created a list of packaged to be downloaded -func (f *Furyconf) Parse(prefix string) ([]Package, error) { - pkgs := make([]Package, 0) - if prefix != "" { - logrus.Infof("prefix is set to '%s', downloading only matching modules", prefix) - } - // First we aggregate all packages in one single list - for _, v := range f.Roles { - v.Kind = "roles" - if strings.HasPrefix(v.Name, prefix) { - logrus.Debugf("role '%s' matches prefix, adding it to the download list", v.Name) - pkgs = append(pkgs, v) - } else { - logrus.Debugf("role '%s' does not match prefix, skipping it", v.Name) - } - } - for _, v := range f.Modules { - v.Kind = "modules" - if strings.HasPrefix(v.Name, prefix) { - logrus.Debugf("module '%s' matches prefix, adding it to the download list", v.Name) - pkgs = append(pkgs, v) - } else { - logrus.Debugf("module '%s' does not match prefix, skipping it", v.Name) - } - } - for _, v := range f.Bases { - v.Kind = "katalog" - if strings.HasPrefix(v.Name, prefix) { - logrus.Debugf("katalog '%s' matches prefix, adding it to the download list", v.Name) - pkgs = append(pkgs, v) - } else { - logrus.Debugf("katalog '%s' does not match prefix, skipping it", v.Name) - } - } - - // Now we parse packages and create a list of directories to be downloaded - for i := range pkgs { - for k, v := range f.Versions { - if strings.HasPrefix(pkgs[i].Name, k) { - pkgs[i].Version = v - break - } - } - - pkgs[i].ProviderKind = f.Provider[pkgs[i].Kind] - pkgs[i].Dir = newDir(f.VendorFolderName, pkgs[i]).getConsumableDirectory() - } - - return pkgs, nil -} - -func (k *ProviderKind) getLabeledURI(providerName, label string) (string, error) { - for name, providerSpecList := range *k { - - if name != providerName { - continue - } - for _, providerMap := range providerSpecList { - - if providerMap.Label != label { - continue - } - - return fmt.Sprintf("git::%s", providerMap.BaseURI), nil - - } - - } - return "", fmt.Errorf("no label %s found", label) -} - -func (k *ProviderKind) pickCloudProviderURL(cloudProvider ProviderOptSpec) string { - - url, err := k.getLabeledURI(cloudProvider.Name, cloudProvider.Label) - - if err != nil { - logrus.Fatal(err) - } - - return url -} - -// DirSpec is the abstraction of the fields needed for generating a destination directory -type DirSpec struct { - VendorFolder string - Kind string - Name string - Registry bool - Provider ProviderOptSpec -} - -func newDir(vendorFolder string, pkg Package) *DirSpec { - return &DirSpec{ - VendorFolder: vendorFolder, - Kind: pkg.Kind, - Name: pkg.Name, - Registry: pkg.Registry, - Provider: pkg.ProviderOpt, - } -} - -// getConsumableDirectory returns a directory we can write to -func (d *DirSpec) getConsumableDirectory() string { - if d.Registry { - return fmt.Sprintf("%s/%s/%s/%s/%s", d.VendorFolder, d.Kind, d.Provider.Label, d.Provider.Name, d.Name) - } - return fmt.Sprintf("%s/%s/%s", d.VendorFolder, d.Kind, d.Name) -} diff --git a/cmd/download.go b/cmd/download.go deleted file mode 100644 index cd39d5c4a..000000000 --- a/cmd/download.go +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "errors" - "fmt" - "net/http" - "os" - "path" - "strings" - "sync" - - "github.com/hashicorp/go-getter" - "github.com/sirupsen/logrus" -) - -var ( - httpsRepoPrefix = "git::https://github.com/sighupio/fury-kubernetes" - sshRepoPrefix = "git@github.com:sighupio/fury-kubernetes" - fallbackHttpsRepoPrefix = "git::https://github.com/sighupio/kubernetes-fury" - fallbackSshRepoPrefix = "git@github.com:sighupio/kubernetes-fury" -) - -type DownloadOpts struct { - Parallel bool - Https bool - Fallback bool -} - -// PackageURL is the representation of the fields needed to elaborate an url -type PackageURL struct { - Prefix string - Blocks []string - Kind string - Version string - Registry bool - CloudProvider ProviderOptSpec - KindSpec ProviderKind -} - -// newUrl initialize the PackageURL struct -func newPackageURL(prefix string, blocks []string, kind, version string, registry bool, cloud ProviderOptSpec, kindSpec ProviderKind) *PackageURL { - return &PackageURL{ - Prefix: prefix, - Registry: registry, - Blocks: blocks, - Kind: kind, - Version: version, - CloudProvider: cloud, - KindSpec: kindSpec, - } -} - -// getConsumableURL returns an url that can be used for download -func (n *PackageURL) getConsumableURL() string { - - if !n.Registry { - return n.getURLFromCompanyRepos() - } - - return fmt.Sprintf("%s/%s%s?ref=%s", n.KindSpec.pickCloudProviderURL(n.CloudProvider), n.Blocks[0], ".git", n.Version) - -} - -func (n *PackageURL) getURLFromCompanyRepos() string { - if len(n.Blocks) == 0 { - return "" - } - - dG := "" - - if strings.HasPrefix(n.Prefix, "git::https") { - dG = ".git" - } - - if len(n.Blocks) == 1 { - return fmt.Sprintf("%s-%s%s//%s?ref=%s", n.Prefix, n.Blocks[0], dG, n.Kind, n.Version) - } - - remainingBlocks := "" - - for i := 1; i < len(n.Blocks); i++ { - remainingBlocks = path.Join(remainingBlocks, n.Blocks[i]) - } - - return fmt.Sprintf("%s-%s%s//%s/%s?ref=%s", n.Prefix, n.Blocks[0], dG, n.Kind, remainingBlocks, n.Version) - -} - -func downloadProcess(wg *sync.WaitGroup, opts DownloadOpts, data Package, errChan chan<- error, i int) { - var pU *PackageURL - var url string - // deferring the worker to be done - defer wg.Done() - - logrus.Debugf("worker %d : received data %v", i, data) - - if opts.Https { - // Create the package URL from the data received to download the package - pU = newPackageURL( - httpsRepoPrefix, - strings.Split(data.Name, "/"), - data.Kind, - data.Version, - data.Registry, - data.ProviderOpt, - data.ProviderKind) - - resp, err := checkRepository(pU) - if err != nil { - errChan <- err - return - } - - if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusNotFound { - o := humanReadableSource(pU.getConsumableURL()) - - // Checking if repository was found otherwise fallback to the old prefix, if fallback fails sends error to tehe error channel - pU.Prefix = fallbackHttpsRepoPrefix - - logrus.Infof("downloading '%s' failed, falling back to '%s' and retrying", o, humanReadableSource(pU.getConsumableURL())) - - if resp, err := checkRepository(pU); err != nil || resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusNotFound { - errChan <- fmt.Errorf("error downloading %s for '%s' version '%s'. Both urls '%s' and '%s' have failed. Please check that the repository exists and that your credentials are correctlly configured.", data.Kind, data.Name, data.Version, o, humanReadableSource(pU.getConsumableURL())) - return - } - - } - - url = pU.getConsumableURL() - if token := os.Getenv("GITHUB_TOKEN"); token != "" && opts.Https { - url = normalizeURLWithToken(pU.getConsumableURL()) - } - } - - if !opts.Https { - pU = newPackageURL( - sshRepoPrefix, - strings.Split(data.Name, "/"), - data.Kind, - data.Version, - data.Registry, - data.ProviderOpt, - data.ProviderKind) - - url = pU.getConsumableURL() - - if err := get(url, data.Dir, getter.ClientModeDir, true); err != nil { - o := humanReadableSource(pU.getConsumableURL()) - - // Checking if repository was found otherwise fallback to the old prefix, if fallback fails sends error to tehe error channel - pU.Prefix = fallbackSshRepoPrefix - - logrus.Infof("downloading '%s' failed, falling back to %s and retrying", o, humanReadableSource(pU.getConsumableURL())) - - url = pU.getConsumableURL() - - if err := get(url, data.Dir, getter.ClientModeDir, true); err != nil { - errChan <- fmt.Errorf("error downloading %s for '%s' version '%s'. Both urls '%s' and '%s' have failed. Please check that the repository exists and that your credentials are correctlly configured. You might want to try using the -H flag", data.Kind, data.Name, data.Version, o, humanReadableSource(pU.getConsumableURL())) - return - } - } - } - - downloadErr := get(url, data.Dir, getter.ClientModeDir, true) - if downloadErr != nil { - if err := os.RemoveAll(data.Dir); err != nil { - logrus.Errorf("error removing directory '%s': %s", data.Dir, err.Error()) - } - errChan <- downloadErr - } -} - -func Download(packages []Package, opts DownloadOpts) error { - //Preparing all the necessary data for a worker pool - var wg sync.WaitGroup - errChan := make(chan error, len(packages)) - jobs := make(chan Package, len(packages)) - - // Populating the job channel with all the packages to download - for _, p := range packages { - jobs <- p - } - - logrus.Debugf("workers = %d", len(jobs)) - - // Starting the workers to download the packages in parallel - for i, data := range packages { - wg.Add(1) - - go downloadProcess(&wg, opts, data, errChan, i) - - logrus.Debugf("created worker %d", i) - } - - // Waiting for all the workers to finish - wg.Wait() - - // Closing the job channel - close(jobs) - - // Closing the error channel - close(errChan) - - logrus.Debugf("finished downloading all packages") - - // Checking if there was any error during the download, if so, print it - if len(errChan) > 0 { - for err := range errChan { - if err != nil { - errString := strings.Replace(err.Error(), "\n", " ", -1) - logrus.Errorln(errString) - } - } - - return errors.New("some downloads have failed. Please check the logs") - } - - return nil -} - -func get(src, dest string, mode getter.ClientMode, cleanGitFolder bool) error { - logrus.Debugf("starting download process for '%s' into '%s'", src, dest) - - pwd, err := os.Getwd() - if err != nil { - return err - } - - client := &getter.Client{ - Src: src, - Dst: dest + ".tmp", - Pwd: pwd, - Mode: mode, - } - - logrus.Debugf("downloading temporary file '%s' into '%s'", client.Src, client.Dst) - - h := humanReadableSource(src) - - logrus.Infof("downloading '%s' into '%s'", h, dest) - - if err := os.RemoveAll(client.Dst); err != nil { - return err - } - - if err := client.Get(); err != nil { - return err - } - - if err := renameDir(client.Dst, dest); err != nil { - return err - } - - if cleanGitFolder { - gitFolder := fmt.Sprintf("%s/.git", dest) - logrus.Infof("removing git subfolder: %s", gitFolder) - if err = os.RemoveAll(gitFolder); err != nil { - return err - } - } - - logrus.Debugf("download process finished: %s -> %s", src, dest) - - return nil -} - -// humanReadableSource returns a human-readable string for the given source -func humanReadableSource(src string) (humanReadableSrc string) { - humanReadableSrc = src - - if strings.Count(src, "@") >= 1 { - // handles git@github.com:sighupio url type - humanReadableSrc = strings.Join(strings.Split(src, ":")[1:], ":") - humanReadableSrc = strings.Replace(humanReadableSrc, "//", "/", 1) - } - - if strings.Count(humanReadableSrc, "//") >= 1 { - // handles git::https://whatever.com//mymodule url type - humanReadableSrc = strings.Join(strings.Split(humanReadableSrc, "//")[1:], "/") - } - - return humanReadableSrc -} - -func renameDir(src string, dest string) error { - if _, err := os.Stat(dest); !os.IsNotExist(err) { - logrus.Infof("removing existing folder: %s", dest) - err = os.RemoveAll(dest) - if err != nil { - logrus.Error(err) - return err - } - } - - return os.Rename(src, dest) -} - -func normalizeURL(src string) string { - var s string - - if strings.HasPrefix(src, "git@") { - s = strings.Split(src, "//")[0] - s = strings.Replace(s, "git@github.com:", "https://github.com/", 1) - } - - if strings.HasPrefix(src, "git::") { - _, s, _ = strings.Cut(src, "git::") - } - - return strings.Split(s, ".git/")[0] -} - -func normalizeURLWithAPI(src string) string { - var s string - - if strings.HasPrefix(src, "git@") { - s = strings.Split(src, "//")[0] - s = strings.Replace(s, "git@github.com:", "https://api.github.com/repos/", 1) - } - - if strings.HasPrefix(src, "git::") { - s = strings.Replace(src, "git::https://github.com/", "https://api.github.com/repos/", 1) - } - - return strings.Split(s, ".git/")[0] -} - -func normalizeURLWithToken(src string) string { - - s := strings.Replace(src, "git::https://", "git::https://oauth2:"+os.Getenv("GITHUB_TOKEN")+"@", 1) - - return s -} - -func checkRepository(pu *PackageURL) (*http.Response, error) { - var url string - - token := os.Getenv("GITHUB_TOKEN") - if token != "" { - url = normalizeURLWithAPI(pu.getConsumableURL()) - } else { - url = normalizeURL(pu.getConsumableURL()) - } - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", "application/vnd.github.v3+json") - gh_token := os.Getenv("GITHUB_TOKEN") - if gh_token != "" { - req.Header.Set("Authorization", "Bearer "+gh_token) - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - // Maybe the repository is public but token is wrong, so we try again without token - if resp.StatusCode == http.StatusUnauthorized { - url = normalizeURL(pu.getConsumableURL()) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - return http.DefaultClient.Do(req) - } - - return resp, nil -} diff --git a/cmd/dump.go b/cmd/dump.go new file mode 100644 index 000000000..d5ae1bda1 --- /dev/null +++ b/cmd/dump.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/cmd/dump" +) + +func NewDumpCmd() *cobra.Command { + dumpCmd := &cobra.Command{ + Use: "dump", + Short: "Dump templates and other useful fury objects", + } + + dumpCmd.AddCommand(dump.NewTemplateCmd()) + + return dumpCmd +} diff --git a/cmd/template.go b/cmd/dump/template.go similarity index 90% rename from cmd/template.go rename to cmd/dump/template.go index a87d33cf3..501d81a6c 100644 --- a/cmd/template.go +++ b/cmd/dump/template.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package cmd +package dump import ( "fmt" @@ -17,11 +17,14 @@ import ( "github.com/sighupio/furyctl/internal/template" ) -var ( - tDryRun bool - tNoOverwrite bool +type templateConfig struct { + DryRun bool + NoOverwrite bool +} - templateCmd = &cobra.Command{ +func NewTemplateCmd() *cobra.Command { + cfg := templateConfig{} + templateCmd := &cobra.Command{ Use: "template", Short: "Renders the distribution's manifests from a template and a configuration file", Long: `Generates a folder with the Kustomization project for deploying Kubernetes Fury Distribution into a cluster. @@ -78,7 +81,7 @@ The generated folder will be created starting from a provided template and the p return err } - if !tNoOverwrite { + if !cfg.NoOverwrite { if err = os.RemoveAll(target); err != nil { return err } @@ -90,8 +93,8 @@ The generated folder will be created starting from a provided template and the p confPath, outDirPath, suffix, - tNoOverwrite, - tDryRun, + cfg.NoOverwrite, + cfg.DryRun, ) if err != nil { return err @@ -100,19 +103,19 @@ The generated folder will be created starting from a provided template and the p return templateModel.Generate() }, } -) -func init() { templateCmd.Flags().BoolVar( - &tDryRun, + &cfg.DryRun, "dry-run", false, "Furyctl will try its best to generate the manifests despite the errors", ) templateCmd.Flags().BoolVar( - &tNoOverwrite, + &cfg.NoOverwrite, "no-overwrite", false, "Stop if target directory is not empty", ) + + return templateCmd } diff --git a/cmd/init.go b/cmd/init.go deleted file mode 100644 index 115a77500..000000000 --- a/cmd/init.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "github.com/sirupsen/logrus" - - getter "github.com/hashicorp/go-getter" - "github.com/spf13/cobra" -) - -const ( - furyFile = "Furyfile.yml" - kustomizationFile = "kustomization.yaml" - httpsDistributionRepoPrefix = "http::https://github.com/sighupio/fury-distribution/releases/download/" -) - -var fileNames = [...]string{furyFile, kustomizationFile} -var distributionVersion string - -func init() { - initCmd.Flags().StringVar(&distributionVersion, "version", "", "Specify the Kubernetes Fury Distribution version") - err := initCmd.MarkFlagRequired("version") - if err != nil { - logrus.Print(err) - } -} - -var initCmd = &cobra.Command{ - Use: "init", - Short: "Initialize the minimum distribution configuration", - Long: "Initialize the current directory with the minimum distribution configuration", - RunE: func(cmd *cobra.Command, args []string) (err error) { - for _, fileName := range fileNames { - url := httpsDistributionRepoPrefix + distributionVersion + "/" + fileName - err = downloadFile(url, fileName) - if err != nil { - return err - } - } - return nil - }, -} - -func downloadFile(url string, outputFileName string) error { - err := get(url, outputFileName, getter.ClientModeFile, false) - if err != nil { - logrus.Print(err) - } - return err -} diff --git a/cmd/root.go b/cmd/root.go index b70d028f5..04dd0a152 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,89 +12,75 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/io" "github.com/sighupio/furyctl/pkg/analytics" ) -var ( - version = "dev" - commit = "none" - date = "unknown" - - s *spinner.Spinner - debug bool - disableAnalytics bool - disableTty bool -) - -// Execute is the main entrypoint of furyctl -func Execute() error { - return rootCmd.Execute() +type rootConfig struct { + Spinner *spinner.Spinner + Debug bool + DisableAnalytics bool + DisableTty bool } -func init() { - rootCmd.AddCommand(bootstrapCmd) - rootCmd.AddCommand(clusterCmd) - rootCmd.AddCommand(completionCmd) - rootCmd.AddCommand(initCmd) - rootCmd.AddCommand(templateCmd) - rootCmd.AddCommand(validateCmd) - rootCmd.AddCommand(vendorCmd) - rootCmd.AddCommand(versionCmd) - - rootCmd.PersistentFlags().Bool("debug", false, "Enables furyctl debug output") - rootCmd.PersistentFlags().BoolVarP(&disableAnalytics, "disable", "d", false, "Disable analytics") - rootCmd.PersistentFlags().BoolVarP(&disableTty, "no-tty", "T", false, "Disable TTY") - - cobra.OnInitialize(func() { - analytics.Version(version) - analytics.Disable(disableAnalytics) - - w := logrus.StandardLogger().Out - if disableTty { - w = io.NewNullWriter() - f := new(logrus.TextFormatter) - f.DisableColors = true - logrus.SetFormatter(f) - } - - s = spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(w)) - }) - - viper.AutomaticEnv() - viper.SetEnvPrefix("furyctl") +type RootCommand struct { + *cobra.Command + config *rootConfig } -func bootstrapLogrus(cmd *cobra.Command) { - d, err := cmd.Flags().GetBool("debug") - if err != nil { - logrus.Fatal(err) - } +func NewRootCommand(versions map[string]string) *RootCommand { + cfg := &rootConfig{} + rootCmd := &RootCommand{ + Command: &cobra.Command{ + Use: "furyctl", + Short: "The multi-purpose command line tool for the Kubernetes Fury Distribution", + Long: `The multi-purpose command line tool for the Kubernetes Fury Distribution. - if d { - logrus.SetLevel(logrus.DebugLevel) - debug = true +Furyctl is a simple CLI tool to: - return +- download and manage the Kubernetes Fury Distribution (KFD) modules +- create and manage Kubernetes Fury clusters +`, + SilenceUsage: true, + SilenceErrors: true, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + // Configure the spinner + w := logrus.StandardLogger().Out + if cobrax.Flag[bool](cmd, "no-tty").(bool) { + w = io.NewNullWriter() + f := new(logrus.TextFormatter) + f.DisableColors = true + logrus.SetFormatter(f) + } + cfg.Spinner = spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(w)) + + // Set log level + if cobrax.Flag[bool](cmd, "debug").(bool) { + logrus.SetLevel(logrus.DebugLevel) + } else { + logrus.SetLevel(logrus.InfoLevel) + } + + // Configure analytics + analytics.Version(versions["version"]) + analytics.Disable(cobrax.Flag[bool](cmd, "disable-analytics").(bool)) + }, + }, + config: cfg, } - logrus.SetLevel(logrus.InfoLevel) -} + viper.AutomaticEnv() + viper.SetEnvPrefix("furyctl") -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "furyctl", - Short: "The multi-purpose command line tool for the Kubernetes Fury Distribution", - Long: `The multi-purpose command line tool for the Kubernetes Fury Distribution. + rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.Debug, "debug", "D", false, "Enables furyctl debug output") + rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.DisableAnalytics, "disable", "d", false, "Disable analytics") + rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.DisableTty, "no-tty", "T", false, "Disable TTY") -Furyctl is a simple CLI tool to: + rootCmd.AddCommand(NewCompletionCmd()) + rootCmd.AddCommand(NewDumpCmd()) + rootCmd.AddCommand(NewValidateCommand(versions["version"])) + rootCmd.AddCommand(NewVersionCmd(versions)) -- download and manage the Kubernetes Fury Distribution (KFD) modules -- create and manage Kubernetes Fury clusters -`, - SilenceUsage: true, - SilenceErrors: true, - PersistentPreRun: func(cmd *cobra.Command, _ []string) { - bootstrapLogrus(cmd) - }, + return rootCmd } diff --git a/cmd/selfprovision.go b/cmd/selfprovision.go deleted file mode 100644 index 4dea39e66..000000000 --- a/cmd/selfprovision.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "bufio" - "errors" - "fmt" - "os" - "strings" - - "github.com/sighupio/furyctl/internal/configuration" - "github.com/sighupio/furyctl/internal/project" - "github.com/sirupsen/logrus" -) - -var ( - stop chan os.Signal - - prj *project.Project - cfg *configuration.Configuration -) - -func parseConfig(path string, kind string) (err error) { - logrus.Debugf("parsing configuration file %v", path) - cfg, err = configuration.Parse(path) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - if cfg.Kind != kind { - errMessage := fmt.Sprintf("error parsing configuration file. Unexpected kind. Got: %v but: %v expected", cfg.Kind, kind) - logrus.Error(errMessage) - return errors.New(errMessage) - } - return nil -} - -func warning(command string) { - fmt.Printf(` - Forced stop of the %v process. - furyctl can not guarantee the correct behavior after this - action. Try to recover the state of the process running: - - $ furyctl %v update - -`, command, command) -} - -func handleStopSignal(command string, c chan os.Signal) { - go func() { - <-c - fmt.Println("\r Are you sure you want to stop it?\n Write 'yes' to force close it. Press enter to continue") - reader := bufio.NewReader(os.Stdin) - text, err := reader.ReadString('\n') - if err != nil { - os.Exit(2) - } - text = strings.ReplaceAll(text, "\n", "") - if strings.Compare("yes", text) == 0 { - warning(command) - os.Exit(1) - } - handleStopSignal(command, c) - }() -} diff --git a/cmd/validate.go b/cmd/validate.go index 453b6eee3..048792a26 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -10,14 +10,14 @@ import ( "github.com/sighupio/furyctl/cmd/validate" ) -var ( - validateCmd = &cobra.Command{ +func NewValidateCommand(version string) *cobra.Command { + validateCmd := &cobra.Command{ Use: "validate", Short: "Validate fury config files and dependencies", } -) -func init() { validateCmd.AddCommand(validate.NewConfigCmd(version)) validateCmd.AddCommand(validate.NewDependenciesCmd(version)) + + return validateCmd } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 934a761a9..e67b32d21 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -12,6 +12,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/schema/santhosh" @@ -26,9 +27,9 @@ func NewConfigCmd(version string) *cobra.Command { Use: "config", Short: "Validate furyctl.yaml file", RunE: func(cmd *cobra.Command, _ []string) error { - debug := flag[bool](cmd, "debug").(bool) - furyctlPath := flag[string](cmd, "config").(string) - distroLocation := flag[string](cmd, "distro-location").(string) + debug := cobrax.Flag[bool](cmd, "debug").(bool) + furyctlPath := cobrax.Flag[string](cmd, "config").(string) + distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) if err != nil { diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index c8ecfdced..4bf5380c9 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -13,6 +13,7 @@ import ( "regexp" "strings" + "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/yaml" @@ -33,10 +34,10 @@ func NewDependenciesCmd(version string) *cobra.Command { Use: "dependencies", Short: "Validate furyctl.yaml file", RunE: func(cmd *cobra.Command, _ []string) error { - debug := flag[bool](cmd, "debug").(bool) - binPath := flag[string](cmd, "bin-path").(string) - furyctlPath := flag[string](cmd, "config").(string) - distroLocation := flag[string](cmd, "distro-location").(string) + debug := cobrax.Flag[bool](cmd, "debug").(bool) + binPath := cobrax.Flag[string](cmd, "bin-path").(string) + furyctlPath := cobrax.Flag[string](cmd, "config").(string) + distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) if err != nil { diff --git a/cmd/validate/util.go b/cmd/validate/util.go index 682fd5f65..d71cfab08 100644 --- a/cmd/validate/util.go +++ b/cmd/validate/util.go @@ -9,12 +9,10 @@ import ( "fmt" "os" "path/filepath" - "strconv" "strings" "github.com/hashicorp/go-getter" "github.com/sirupsen/logrus" - "github.com/spf13/cobra" "github.com/sighupio/furyctl/internal/distribution" ) @@ -37,34 +35,6 @@ var ( ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) -func flag[T bool | int | string](cmd *cobra.Command, name string) any { - var f T - - if cmd == nil { - return f - } - - if cmd.Flag(name) == nil { - return f - } - - v := cmd.Flag(name).Value.String() - - if v == "true" { - return true - } - - if v == "false" { - return false - } - - if vv, err := strconv.Atoi(v); err == nil { - return vv - } - - return v -} - func getSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, error) { avp := strings.Split(conf.ApiVersion, "/") diff --git a/cmd/vendor.go b/cmd/vendor.go deleted file mode 100644 index d5c1d2f81..000000000 --- a/cmd/vendor.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "os" - "strings" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func init() { - vendorCmd.PersistentFlags().BoolVarP(&conf.DownloadOpts.Https, "https", "H", false, "download using HTTPS instead of SSH protocol. Use when SSH traffic is being blocked or when SSH client has not been configured\nset the GITHUB_TOKEN environment variable with your token to use authentication while downloading, for example for private repositories") - vendorCmd.PersistentFlags().StringVarP(&conf.Prefix, "prefix", "P", "", "download modules that start with prefix only to reduce download scope. Example:\nfuryctl vendor -P mon\nwill download all modules that start with 'mon', like 'monitoring', and ignore the rest") - rootCmd.AddCommand(vendorCmd) -} - -var conf = Config{} - -type Config struct { - Packages []Package - DownloadOpts DownloadOpts - Prefix string -} - -// vendorCmd represents the vendor command -var vendorCmd = &cobra.Command{ - Use: "vendor", - Short: "download KFD modules and dependencies specified in Furyfile.yml", - Long: "download KFD modules and dependencies specified in Furyfile.yml", - SilenceUsage: true, - SilenceErrors: true, - RunE: func(_ *cobra.Command, _ []string) error { - viper.SetConfigType("yml") - viper.AddConfigPath(".") - viper.SetConfigName(configFile) - config := new(Furyconf) - - if err := viper.ReadInConfig(); err != nil { - return err - } - - if err := viper.Unmarshal(config); err != nil { - return err - } - - if err := config.Validate(); err != nil { - return err - } - - list, err := config.Parse(conf.Prefix) - if err != nil { - return err - } - - if token := os.Getenv("GITHUB_TOKEN"); strings.Contains(token, " ") { - logrus.Warn("GITHUB_TOKEN contains a space character. As a result, vendoring modules may fail. If it's intended, you can ignore this warning.\n") - } - - for _, p := range list { - if p.Version == "" { - logrus.Warnf("package '%s' has no version specified. Will download the default git branch", p.Name) - } else { - logrus.Infof("using version '%v' for package '%s'", p.Version, p.Name) - } - } - - return Download(list, conf.DownloadOpts) - }, -} diff --git a/cmd/version.go b/cmd/version.go index b2b5dab83..63c30b78b 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -5,17 +5,19 @@ package cmd import ( - "github.com/sirupsen/logrus" + "fmt" + "github.com/spf13/cobra" ) -// versionCmd represents the version command -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Prints the client version information", - Long: ``, - Run: func(_ *cobra.Command, _ []string) { - logrus.Printf("Furyctl version %v\n", version) - logrus.Printf("built %v from commit %v", date, commit) - }, +func NewVersionCmd(versions map[string]string) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the version number of furyctl", + Run: func(_ *cobra.Command, _ []string) { + for k, v := range versions { + fmt.Printf("%s: %s\n", k, v) + } + }, + } } diff --git a/internal/cobrax/flags.go b/internal/cobrax/flags.go new file mode 100644 index 000000000..4dadb9149 --- /dev/null +++ b/internal/cobrax/flags.go @@ -0,0 +1,35 @@ +package cobrax + +import ( + "strconv" + + "github.com/spf13/cobra" +) + +func Flag[T bool | int | string](cmd *cobra.Command, name string) any { + var f T + + if cmd == nil { + return f + } + + if cmd.Flag(name) == nil { + return f + } + + v := cmd.Flag(name).Value.String() + + if v == "true" { + return true + } + + if v == "false" { + return false + } + + if vv, err := strconv.Atoi(v); err == nil { + return vv + } + + return v +} diff --git a/internal/configuration/assets/eks-cluster.yml b/internal/configuration/assets/eks-cluster.yml index f9ee2b341..92301811e 100644 --- a/internal/configuration/assets/eks-cluster.yml +++ b/internal/configuration/assets/eks-cluster.yml @@ -15,10 +15,10 @@ executor: region: "eu-central-1" provisioner: eks spec: - version: "1.18" + version: "1.23" logRetentionDays: 30 network: "vpc-1" - subnetworks: + subnetworks: - "subnet-1" - "subnet-2" - "subnet-3" @@ -27,7 +27,7 @@ spec: nodePoolsLaunchKind: "launch_template" nodePools: - name: "one" - version: "1.18" + version: "1.23" minSize: 0 maxSize: 10 instanceType: "m" diff --git a/internal/configuration/config_test.go b/internal/configuration/config_test.go index 64a5fcf93..8e34209d7 100644 --- a/internal/configuration/config_test.go +++ b/internal/configuration/config_test.go @@ -53,17 +53,16 @@ func init() { } sampleEKSConfig.Provisioner = "eks" sampleEKSConfig.Spec = clustercfg.EKS{ - Version: "1.18", - Network: "vpc-1", - LogRetentionDays: 30, - SubNetworks: []string{"subnet-1", "subnet-2", "subnet-3"}, - DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"0.0.0.0/0"}}, - SSHPublicKey: "123", - NodePoolsLaunchKind: "launch_template", + Version: "1.23", + Network: "vpc-1", + SubNetworks: []string{"subnet-1", "subnet-2", "subnet-3"}, + DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"0.0.0.0/0"}}, + SSHPublicKey: "123", + LogRetentionDays: 30, NodePools: []clustercfg.EKSNodePool{ { Name: "one", - Version: "1.18", + Version: "1.23", MinSize: 0, MaxSize: 10, InstanceType: "m", diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go index e15a01033..ed17ca37b 100644 --- a/internal/configuration/templates.go +++ b/internal/configuration/templates.go @@ -157,7 +157,7 @@ func clusterTemplate(config *Configuration) error { NodePools: []clustercfg.EKSNodePool{ { Name: "my-node-pool. Required. Name of the node pool", - Version: "1.18. Required. null to use Control Plane version.", + Version: "1.23. Required. null to use Control Plane version.", MinSize: 0, MaxSize: 1, InstanceType: "t3.micro. Required. AWS EC2 isntance types", @@ -191,7 +191,7 @@ func clusterTemplate(config *Configuration) error { config.Spec = spec case config.Provisioner == "gke": spec := clustercfg.GKE{ - Version: "1.18.12-gke.1206 # GKE Control plane version", + Version: "1.23.12-gke.1206 # GKE Control plane version", Network: "vpc-id1 # Identificator of the Network", NetworkProjectID: "12309123 # OPTIONAL. The project ID of the shared VPC's host (for shared vpc support)", ControlPlaneCIDR: "10.0.0.0/28 # OPTIONAL. DEFAULT VALUE. The IP range in CIDR notation to use for the hosted master network", @@ -213,7 +213,7 @@ func clusterTemplate(config *Configuration) error { NodePools: []clustercfg.GKENodePool{ { Name: "my-node-pool. Required. Name of the node pool", - Version: "1.18.12-gke.1206. Required. null to use Control Plane version.", + Version: "1.23.12-gke.1206. Required. null to use Control Plane version.", MinSize: 1, MaxSize: 1, InstanceType: "n1-standard-1. Required. GCP instance types", diff --git a/main.go b/main.go index b2b91980d..053ed68c2 100644 --- a/main.go +++ b/main.go @@ -15,16 +15,28 @@ package main import ( - "os" - + "github.com/sighupio/furyctl/cmd" "github.com/sirupsen/logrus" +) - "github.com/sighupio/furyctl/cmd" +var ( + version = "unknown" + gitCommit = "unknown" + buildTime = "unknown" + goVersion = "unknown" + osArch = "unknown" ) func main() { - if err := cmd.Execute(); err != nil { - logrus.Error(err) - os.Exit(1) + versions := map[string]string{ + "version": version, + "gitCommit": gitCommit, + "buildTime": buildTime, + "goVersion": goVersion, + "osArch": osArch, + } + + if err := cmd.NewRootCommand(versions).Execute(); err != nil { + logrus.Fatal(err) } } From ea46a162c20c4f9857b79efa62e71513d2a716d6 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:21:08 +0200 Subject: [PATCH 019/383] chore: reformat code --- cmd/dump/template.go | 2 +- cmd/validate/config.go | 2 +- cmd/validate/config_test.go | 7 +++--- cmd/validate/dependencies.go | 10 ++++---- cmd/validate/util_test.go | 2 +- internal/bootstrap/bootstrap.go | 1 - .../bootstrap/provisioners/aws/provisioner.go | 2 +- .../bootstrap/provisioners/gcp/provisioner.go | 2 +- internal/cluster/cluster.go | 1 - .../cluster/provisioners/eks/provisioner.go | 2 +- .../cluster/provisioners/gke/provisioner.go | 2 +- .../provisioners/vsphere/provisioner.go | 14 +++++------ internal/configuration/config_test.go | 8 +++---- internal/configuration/templates.go | 2 +- internal/io/fs.go | 5 ++-- internal/io/nullwriter.go | 3 +-- internal/merge/model.go | 1 + internal/project/project.go | 4 ++-- internal/provisioners/provisioners.go | 1 + internal/schema/santhosh/loader.go | 4 +--- internal/semver/compare.go | 1 - internal/template/funcmap_test.go | 3 ++- internal/template/generator.go | 4 ++-- internal/template/mapper/mapper_test.go | 5 ++-- internal/template/model.go | 2 +- internal/template/model_test.go | 5 ++-- internal/template/node.go | 24 +++++++++---------- internal/template/node_test.go | 5 ++-- pkg/terraform/install.go | 6 ++--- pkg/terraform/terraform.go | 10 ++++---- 30 files changed, 70 insertions(+), 70 deletions(-) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 501d81a6c..fb8cbb408 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -32,7 +32,7 @@ The generated folder will be created starting from a provided template and the p SilenceUsage: true, SilenceErrors: true, RunE: func(_ *cobra.Command, _ []string) error { - //TODO(rm-2470): To be reworked in redmine task - Define template command flags. + // TODO(rm-2470): To be reworked in redmine task - Define template command flags. source := "source" target := "target" suffix := ".tpl" diff --git a/cmd/validate/config.go b/cmd/validate/config.go index e67b32d21..20fc9395d 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -143,7 +143,7 @@ func NewConfigCmd(version string) *cobra.Command { return cmd } -func mergeConfigAndDefaults(furyctlFilePath string, defaultsFilePath string) (string, error) { +func mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) if err != nil { return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) diff --git a/cmd/validate/config_test.go b/cmd/validate/config_test.go index 7dd7190a3..e4d3c13a5 100644 --- a/cmd/validate/config_test.go +++ b/cmd/validate/config_test.go @@ -8,12 +8,13 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/sighupio/furyctl/cmd/validate" - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/yaml" "os" "path/filepath" "testing" + + "github.com/sighupio/furyctl/cmd/validate" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/yaml" ) var ( diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 4bf5380c9..7e4028eec 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -199,7 +199,7 @@ func validateSystemDependencies(kfdManifest distribution.Manifest, binPath strin return nil } -func checkAnsibleVersion(wantVer string, binPath string) error { +func checkAnsibleVersion(wantVer, binPath string) error { if wantVer == "" { return fmt.Errorf("ansible: %w", ErrEmptyToolVersion) } @@ -235,7 +235,7 @@ func checkAnsibleVersion(wantVer string, binPath string) error { return nil } -func checkTerraformVersion(wantVer string, binPath string) error { +func checkTerraformVersion(wantVer, binPath string) error { if wantVer == "" { return fmt.Errorf("terraform: %w", ErrEmptyToolVersion) } @@ -271,7 +271,7 @@ func checkTerraformVersion(wantVer string, binPath string) error { return nil } -func checkKubectlVersion(wantVer string, binPath string) error { +func checkKubectlVersion(wantVer, binPath string) error { if wantVer == "" { return fmt.Errorf("kubectl: %w", ErrEmptyToolVersion) } @@ -310,7 +310,7 @@ func checkKubectlVersion(wantVer string, binPath string) error { return nil } -func checkKustomizeVersion(wantVer string, binPath string) error { +func checkKustomizeVersion(wantVer, binPath string) error { if wantVer == "" { return fmt.Errorf("kustomize: %w", ErrEmptyToolVersion) } @@ -346,7 +346,7 @@ func checkKustomizeVersion(wantVer string, binPath string) error { return nil } -func checkFuryagentVersion(wantVer string, binPath string) error { +func checkFuryagentVersion(wantVer, binPath string) error { if wantVer == "" { return fmt.Errorf("furyagent: %w", ErrEmptyToolVersion) } diff --git a/cmd/validate/util_test.go b/cmd/validate/util_test.go index 89ec91346..e373181dc 100644 --- a/cmd/validate/util_test.go +++ b/cmd/validate/util_test.go @@ -133,7 +133,7 @@ func TestClientGet(t *testing.T) { in := filepath.Join(tmpDir, "in") out := filepath.Join(tmpDir, "out") - if err := os.MkdirAll(in, 0755); err != nil { + if err := os.MkdirAll(in, 0o755); err != nil { t.Fatalf("error creating temp dir: %v", err) } diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 10a40ef07..fba18a089 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -417,7 +417,6 @@ func (c *Bootstrap) output() ([]byte, error) { } func (c *Bootstrap) createGitFiles() error { - c.s.Stop() c.s.Suffix = " Creating .gitattributes file" c.s.Start() diff --git a/internal/bootstrap/provisioners/aws/provisioner.go b/internal/bootstrap/provisioners/aws/provisioner.go index 650ec08e6..17676de78 100644 --- a/internal/bootstrap/provisioners/aws/provisioner.go +++ b/internal/bootstrap/provisioners/aws/provisioner.go @@ -168,7 +168,7 @@ func (d AWS) createVarFile() (err error) { buffer.WriteString(fmt.Sprintf("vpn_ssh_users = [\"%v\"]\n", strings.Join(spec.VPN.SSHUsers, "\",\""))) } - err = ioutil.WriteFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()), buffer.Bytes(), 0600) + err = ioutil.WriteFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()), buffer.Bytes(), 0o600) if err != nil { return err } diff --git a/internal/bootstrap/provisioners/gcp/provisioner.go b/internal/bootstrap/provisioners/gcp/provisioner.go index c45e1fafd..410a489ea 100644 --- a/internal/bootstrap/provisioners/gcp/provisioner.go +++ b/internal/bootstrap/provisioners/gcp/provisioner.go @@ -202,7 +202,7 @@ func (d GCP) createVarFile() (err error) { buffer.WriteString(fmt.Sprintf("vpn_ssh_users = [\"%v\"]\n", strings.Join(spec.VPN.SSHUsers, "\",\""))) } - err = ioutil.WriteFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()), buffer.Bytes(), 0600) + err = ioutil.WriteFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()), buffer.Bytes(), 0o600) if err != nil { return err } diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index 9d86b9842..25664327c 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -441,7 +441,6 @@ func (c *Cluster) output() ([]byte, error) { } func (c *Cluster) createGitFiles() error { - c.s.Stop() c.s.Suffix = " Creating .gitattributes file" c.s.Start() diff --git a/internal/cluster/provisioners/eks/provisioner.go b/internal/cluster/provisioners/eks/provisioner.go index 9062bf550..1532a372d 100644 --- a/internal/cluster/provisioners/eks/provisioner.go +++ b/internal/cluster/provisioners/eks/provisioner.go @@ -268,7 +268,7 @@ func (e EKS) createVarFile() (err error) { } buffer.WriteString("]\n") } - err = ioutil.WriteFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0600) + err = ioutil.WriteFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0o600) if err != nil { return err } diff --git a/internal/cluster/provisioners/gke/provisioner.go b/internal/cluster/provisioners/gke/provisioner.go index 60f3d52eb..a68652559 100644 --- a/internal/cluster/provisioners/gke/provisioner.go +++ b/internal/cluster/provisioners/gke/provisioner.go @@ -220,7 +220,7 @@ func (e GKE) createVarFile() (err error) { buffer.WriteString(fmt.Sprintf("gke_add_cluster_firewall_rules = %v\n", spec.AdditionalClusterFirewallRules)) buffer.WriteString(fmt.Sprintf("gke_disable_default_snat = %v\n", spec.DisableDefaultSNAT)) - err = ioutil.WriteFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0600) + err = ioutil.WriteFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0o600) if err != nil { return err } diff --git a/internal/cluster/provisioners/vsphere/provisioner.go b/internal/cluster/provisioners/vsphere/provisioner.go index 9581758fb..10d21fce3 100644 --- a/internal/cluster/provisioners/vsphere/provisioner.go +++ b/internal/cluster/provisioners/vsphere/provisioner.go @@ -262,7 +262,7 @@ func (e VSphere) createVarFile() (err error) { buffer.WriteString("]\n") } - err = ioutil.WriteFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0600) + err = ioutil.WriteFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0o600) if err != nil { return err } @@ -341,7 +341,7 @@ func downloadAnsibleRoles(workingDirectory string) error { downloadPath := filepath.Join(workingDirectory, "provision/roles") logrus.Infof("Ansible roles download path: %v", downloadPath) - if err := os.Mkdir(downloadPath, 0755); err != nil { + if err := os.Mkdir(downloadPath, 0o755); err != nil { return err } @@ -418,13 +418,13 @@ func (e VSphere) Update() (string, error) { } filePath := filepath.Join(e.terraform.WorkingDir(), "provision/hosts.ini") - err = ioutil.WriteFile(filePath, []byte(ansibleInventory), 0644) + err = ioutil.WriteFile(filePath, []byte(ansibleInventory), 0o644) if err != nil { return "", err } filePath = filepath.Join(e.terraform.WorkingDir(), "provision/haproxy.cfg") - err = ioutil.WriteFile(filePath, []byte(haproxyConfig), 0644) + err = ioutil.WriteFile(filePath, []byte(haproxyConfig), 0o644) if err != nil { return "", err } @@ -466,7 +466,7 @@ func createPKI(workingDirectory string) error { logrus.Infof("Download furyagent: %v", downloadPath) - if err := os.MkdirAll(downloadPath, 0755); err != nil { + if err := os.MkdirAll(downloadPath, 0o755); err != nil { return err } @@ -491,7 +491,7 @@ func createPKI(workingDirectory string) error { logrus.Fatal(err) } - os.Chmod(filepath.Join(downloadPath, wantedExecutableName), 0755) + os.Chmod(filepath.Join(downloadPath, wantedExecutableName), 0o755) cmd := exec.Command("./furyagent", "init", "master") cmd.Dir = downloadPath @@ -512,7 +512,7 @@ func createPKI(workingDirectory string) error { return nil } -func runAnsiblePlaybook(workingDir string, logDir string) (string, error) { +func runAnsiblePlaybook(workingDir, logDir string) (string, error) { logrus.Infof("Run Ansible playbook in : %v", workingDir) // TODO: Get the debug flag from the CLI to output both to a file and stdout diff --git a/internal/configuration/config_test.go b/internal/configuration/config_test.go index 8e34209d7..ebb33daea 100644 --- a/internal/configuration/config_test.go +++ b/internal/configuration/config_test.go @@ -12,11 +12,12 @@ import ( clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" ) -var sampleEKSConfig Configuration -var sampleAWSBootstrap Configuration +var ( + sampleEKSConfig Configuration + sampleAWSBootstrap Configuration +) func init() { - sampleAWSBootstrap.Kind = "Bootstrap" sampleAWSBootstrap.Metadata = Metadata{ Name: "my-aws-poc", @@ -83,7 +84,6 @@ func init() { } func TestParseClusterConfigurationFile(t *testing.T) { - sampleAWSBootstrapLocalState := sampleAWSBootstrap sampleAWSBootstrapLocalState.Executor.StateConfiguration.Backend = "local" sampleAWSBootstrapLocalState.Executor.StateConfiguration.Config = map[string]string{ diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go index ed17ca37b..bbb2e9006 100644 --- a/internal/configuration/templates.go +++ b/internal/configuration/templates.go @@ -16,7 +16,7 @@ import ( ) // Template generates a yaml with a sample configuration requested by the client -func Template(kind string, provisioner string) (string, error) { +func Template(kind, provisioner string) (string, error) { config := Configuration{} config.Kind = kind config.Provisioner = provisioner diff --git a/internal/io/fs.go b/internal/io/fs.go index e526e2a3e..e13c4758a 100644 --- a/internal/io/fs.go +++ b/internal/io/fs.go @@ -7,11 +7,12 @@ package io import ( "bytes" "fmt" - "github.com/sirupsen/logrus" "io" "os" "path/filepath" "strings" + + "github.com/sirupsen/logrus" ) func CheckDirIsEmpty(target string) error { @@ -29,7 +30,7 @@ func CheckDirIsEmpty(target string) error { } func AppendBufferToFile(b bytes.Buffer, target string) error { - destination, err := os.OpenFile(target, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + destination, err := os.OpenFile(target, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return err } diff --git a/internal/io/nullwriter.go b/internal/io/nullwriter.go index e91ccf384..55f8365ad 100644 --- a/internal/io/nullwriter.go +++ b/internal/io/nullwriter.go @@ -8,8 +8,7 @@ func NewNullWriter() *NullWriter { return &NullWriter{} } -type NullWriter struct { -} +type NullWriter struct{} func (nw *NullWriter) Write(p []byte) (n int, err error) { return 0, nil diff --git a/internal/merge/model.go b/internal/merge/model.go index 282cdd540..c2af50119 100644 --- a/internal/merge/model.go +++ b/internal/merge/model.go @@ -27,6 +27,7 @@ func NewDefaultModel(content map[any]any, path string) *DefaultModel { path: path, } } + func (b *DefaultModel) Content() map[any]any { return b.content } diff --git a/internal/project/project.go b/internal/project/project.go index 53f3a7083..f8dc196e2 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -17,8 +17,8 @@ import ( const ( pathAlreadyExistsErr = "Directory already exists" pathCreationErr = "Path dir couldn't be created. %v" - defaultDirPermission = 0700 - defaultFilePermission = 0600 + defaultDirPermission = 0o700 + defaultFilePermission = 0o600 ) // Project represents a simple structure with a couple of useful methods to init a project diff --git a/internal/provisioners/provisioners.go b/internal/provisioners/provisioners.go index 776e61c25..9e11e89dc 100644 --- a/internal/provisioners/provisioners.go +++ b/internal/provisioners/provisioners.go @@ -66,6 +66,7 @@ func getClusterProvisioner(config configuration.Configuration) (Provisioner, err return nil, errors.New("Provisioner not found") } } + func getBootstrapProvisioner(config configuration.Configuration) (Provisioner, error) { switch { case config.Provisioner == "aws": diff --git a/internal/schema/santhosh/loader.go b/internal/schema/santhosh/loader.go index a4efed6b7..8d7dd3df8 100644 --- a/internal/schema/santhosh/loader.go +++ b/internal/schema/santhosh/loader.go @@ -13,9 +13,7 @@ import ( "github.com/santhosh-tekuri/jsonschema" ) -var ( - ErrCannotLoadSchema = errors.New("failed to load schema file") -) +var ErrCannotLoadSchema = errors.New("failed to load schema file") func LoadSchema(schemaPath string) (*jsonschema.Schema, error) { berr := fmt.Errorf("%w '%s'", ErrCannotLoadSchema, schemaPath) diff --git a/internal/semver/compare.go b/internal/semver/compare.go index 09c7ef36d..bdc03d56e 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -82,5 +82,4 @@ func isValid(v string) bool { } return true - } diff --git a/internal/template/funcmap_test.go b/internal/template/funcmap_test.go index 89cad0f19..cb3bd1fbe 100644 --- a/internal/template/funcmap_test.go +++ b/internal/template/funcmap_test.go @@ -5,9 +5,10 @@ package template_test import ( + "testing" + "github.com/sighupio/furyctl/internal/template" "github.com/stretchr/testify/assert" - "testing" ) func TestNewFuncMap(t *testing.T) { diff --git a/internal/template/generator.go b/internal/template/generator.go index 58ab08a93..79fcf23a6 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -96,7 +96,7 @@ func (g *generator) ProcessFilename( ) (string, error) { var realTarget string - if tm.Config.Templates.ProcessFilename { //try to process filename as template + if tm.Config.Templates.ProcessFilename { // try to process filename as template tpl := template.Must( template.New("currentTarget").Funcs(g.funcMap.FuncMap).Parse(g.target)) @@ -112,7 +112,7 @@ func (g *generator) ProcessFilename( suf := tm.Suffix if strings.HasSuffix(realTarget, suf) { - realTarget = realTarget[:len(realTarget)-len(tm.Suffix)] //cut off extension (.tmpl) from the end + realTarget = realTarget[:len(realTarget)-len(tm.Suffix)] // cut off extension (.tmpl) from the end } return realTarget, nil diff --git a/internal/template/mapper/mapper_test.go b/internal/template/mapper/mapper_test.go index 3c16d0533..0a85a169b 100644 --- a/internal/template/mapper/mapper_test.go +++ b/internal/template/mapper/mapper_test.go @@ -6,10 +6,11 @@ package mapper_test import ( "fmt" - "github.com/sighupio/furyctl/internal/template/mapper" - "github.com/stretchr/testify/assert" "os" "testing" + + "github.com/sighupio/furyctl/internal/template/mapper" + "github.com/stretchr/testify/assert" ) func TestNewMapper(t *testing.T) { diff --git a/internal/template/model.go b/internal/template/model.go index df1fabd26..f12f53a36 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -149,7 +149,7 @@ func (tm *Model) applyTemplates( ) realTarget, fErr := gen.ProcessFilename(tm) - if fErr != nil { //maybe we should fail back to real name instead? + if fErr != nil { // maybe we should fail back to real name instead? return fErr } diff --git a/internal/template/model_test.go b/internal/template/model_test.go index 3f148d628..25ebac716 100644 --- a/internal/template/model_test.go +++ b/internal/template/model_test.go @@ -5,11 +5,12 @@ package template_test import ( + "os" + "testing" + "github.com/sighupio/furyctl/internal/template" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" - "os" - "testing" ) func TestNewTemplateModel(t *testing.T) { diff --git a/internal/template/node.go b/internal/template/node.go index a7995b900..7c1a50667 100644 --- a/internal/template/node.go +++ b/internal/template/node.go @@ -10,19 +10,17 @@ import ( "text/template/parse" ) -var ( - // MapParseNodeToAlias is a map of parse.Node to its alias. - MapParseNodeToAlias = map[parse.NodeType]interface{}{ - parse.NodeList: &ListNode{}, - parse.NodeRange: &RangeNode{}, - parse.NodePipe: &PipeNode{}, - parse.NodeTemplate: &TemplateNode{}, - parse.NodeIf: &IfNode{}, - parse.NodeAction: &ActionNode{}, - parse.NodeField: &FieldNode{}, - parse.NodeVariable: &VariableNode{}, - } -) +// MapParseNodeToAlias is a map of parse.Node to its alias. +var MapParseNodeToAlias = map[parse.NodeType]interface{}{ + parse.NodeList: &ListNode{}, + parse.NodeRange: &RangeNode{}, + parse.NodePipe: &PipeNode{}, + parse.NodeTemplate: &TemplateNode{}, + parse.NodeIf: &IfNode{}, + parse.NodeAction: &ActionNode{}, + parse.NodeField: &FieldNode{}, + parse.NodeVariable: &VariableNode{}, +} type Node struct { Fields []string diff --git a/internal/template/node_test.go b/internal/template/node_test.go index 46e272c49..dc638192b 100644 --- a/internal/template/node_test.go +++ b/internal/template/node_test.go @@ -5,12 +5,13 @@ package template_test import ( - template2 "github.com/sighupio/furyctl/internal/template" - "github.com/stretchr/testify/assert" "reflect" "testing" "text/template" "text/template/parse" + + template2 "github.com/sighupio/furyctl/internal/template" + "github.com/stretchr/testify/assert" ) func TestNewNode(t *testing.T) { diff --git a/pkg/terraform/install.go b/pkg/terraform/install.go index a221d9c1e..fb4b2a97e 100644 --- a/pkg/terraform/install.go +++ b/pkg/terraform/install.go @@ -20,7 +20,7 @@ import ( ) // ensure ensures a working terraform version to be used in the project -func ensure(terraformVersion string, terraformDownloadPath string) (binPath string, err error) { +func ensure(terraformVersion, terraformDownloadPath string) (binPath string, err error) { if terraformVersion != "" { logrus.Debugf("Installing terraform %v version", terraformVersion) return install(terraformVersion, terraformDownloadPath) @@ -29,7 +29,7 @@ func ensure(terraformVersion string, terraformDownloadPath string) (binPath stri return installLatest(terraformDownloadPath) } -func alreadyAvailable(terraformVersion string, terraformDownloadPath string) (bool, string) { +func alreadyAvailable(terraformVersion, terraformDownloadPath string) (bool, string) { // validate version v, err := version.NewVersion(terraformVersion) if err != nil { @@ -78,7 +78,7 @@ func alreadyAvailable(terraformVersion string, terraformDownloadPath string) (bo return true, binPath } -func install(terraformVersion string, terraformDownloadPath string) (binPath string, err error) { +func install(terraformVersion, terraformDownloadPath string) (binPath string, err error) { ready, binPath := alreadyAvailable(terraformVersion, terraformDownloadPath) if !ready { err := utils.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index 37126bc07..e0dda8b5e 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -105,7 +105,7 @@ func envMap(environ []string) map[string]string { return env } -func configureLogger(tf *tfexec.Terraform, workingDir string, logDir string, debug bool) (err error) { +func configureLogger(tf *tfexec.Terraform, workingDir, logDir string, debug bool) (err error) { logFile, err := os.Create(fmt.Sprintf("%v/%v/terraform.logs", workingDir, logDir)) tf.SetLogger(logrus.StandardLogger()) c := &tfwriter{ @@ -122,16 +122,16 @@ func configureLogger(tf *tfexec.Terraform, workingDir string, logDir string, deb } // configureGitHubNetrcAccess creates the .netrc file with the credentials to access github private repos -func configureGitHubNetrcAccess(path string, token string, configDir string) (err error) { +func configureGitHubNetrcAccess(path, token, configDir string) (err error) { netrc := fmt.Sprintf(`machine github.com login furyctl password %v `, token) - return ioutil.WriteFile(fmt.Sprintf("%v/%v/.netrc", path, configDir), []byte(netrc), os.FileMode(0644)) + return ioutil.WriteFile(fmt.Sprintf("%v/%v/.netrc", path, configDir), []byte(netrc), os.FileMode(0o644)) } // CreateBackendFile creates the backend.tf terraform file with the backend configuration chosen -func createBackendFile(path string, backend string, backendConfig map[string]string) (err error) { +func createBackendFile(path, backend string, backendConfig map[string]string) (err error) { var backendFilebuffer bytes.Buffer backendFilebuffer.WriteString(fmt.Sprintf(`terraform { backend "%v" { @@ -142,7 +142,7 @@ func createBackendFile(path string, backend string, backendConfig map[string]str backendFilebuffer.WriteString(` } }`) backendFileContent := backendFilebuffer.Bytes() - return ioutil.WriteFile(fmt.Sprintf("%v/backend.tf", path), backendFileContent, os.FileMode(0644)) + return ioutil.WriteFile(fmt.Sprintf("%v/backend.tf", path), backendFileContent, os.FileMode(0o644)) } type tfwriter struct { From 21bb589173941d6c38dd958178721de823245b76 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:24:14 +0200 Subject: [PATCH 020/383] chore: reformat imports --- cmd/dump/template.go | 4 ++-- cmd/validate/dependencies.go | 5 +++-- internal/bootstrap/bootstrap.go | 3 ++- internal/bootstrap/provisioners/aws/provisioner.go | 3 ++- internal/bootstrap/provisioners/gcp/provisioner.go | 3 ++- internal/cluster/cluster.go | 3 ++- internal/cluster/provisioners/vsphere/provisioner.go | 3 ++- internal/configuration/config.go | 6 +++--- internal/configuration/templates.go | 6 +++--- internal/project/project.go | 3 ++- internal/provisioners/provisioners.go | 3 ++- internal/template/funcmap_test.go | 3 ++- internal/template/generator.go | 3 ++- internal/template/mapper/mapper_test.go | 3 ++- internal/template/model.go | 4 ++-- internal/template/model_test.go | 3 ++- internal/template/node_test.go | 3 ++- internal/yaml/yaml_test.go | 3 ++- main.go | 3 ++- pkg/terraform/install.go | 3 ++- 20 files changed, 43 insertions(+), 27 deletions(-) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index fb8cbb408..e7528b208 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -9,12 +9,12 @@ import ( "os" "path/filepath" - "github.com/sighupio/furyctl/internal/merge" - "github.com/sighupio/furyctl/internal/yaml" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" + "github.com/sighupio/furyctl/internal/yaml" ) type templateConfig struct { diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 7e4028eec..c596d9eae 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -13,12 +13,13 @@ import ( "regexp" "strings" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/yaml" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" ) var ( diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index fba18a089..56afeb853 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -12,11 +12,12 @@ import ( "github.com/briandowns/spinner" "github.com/hashicorp/terraform-exec/tfexec" + "github.com/sirupsen/logrus" + "github.com/sighupio/furyctl/internal/configuration" "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/internal/provisioners" "github.com/sighupio/furyctl/pkg/terraform" - "github.com/sirupsen/logrus" ) const initExecutorMessage = " Initializing the terraform executor" diff --git a/internal/bootstrap/provisioners/aws/provisioner.go b/internal/bootstrap/provisioners/aws/provisioner.go index 17676de78..27a47da6e 100644 --- a/internal/bootstrap/provisioners/aws/provisioner.go +++ b/internal/bootstrap/provisioners/aws/provisioner.go @@ -14,9 +14,10 @@ import ( "github.com/gobuffalo/packr/v2" "github.com/hashicorp/terraform-exec/tfexec" + "github.com/sirupsen/logrus" + cfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" "github.com/sighupio/furyctl/internal/configuration" - "github.com/sirupsen/logrus" ) // InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated diff --git a/internal/bootstrap/provisioners/gcp/provisioner.go b/internal/bootstrap/provisioners/gcp/provisioner.go index 410a489ea..845e6b48c 100644 --- a/internal/bootstrap/provisioners/gcp/provisioner.go +++ b/internal/bootstrap/provisioners/gcp/provisioner.go @@ -14,9 +14,10 @@ import ( "github.com/gobuffalo/packr/v2" "github.com/hashicorp/terraform-exec/tfexec" + "github.com/sirupsen/logrus" + cfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" "github.com/sighupio/furyctl/internal/configuration" - "github.com/sirupsen/logrus" ) // InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index 25664327c..3932ef6fd 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -12,11 +12,12 @@ import ( "github.com/briandowns/spinner" "github.com/hashicorp/terraform-exec/tfexec" + "github.com/sirupsen/logrus" + "github.com/sighupio/furyctl/internal/configuration" "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/internal/provisioners" "github.com/sighupio/furyctl/pkg/terraform" - "github.com/sirupsen/logrus" ) const initExecutorMessage = " Initializing the terraform executor" diff --git a/internal/cluster/provisioners/vsphere/provisioner.go b/internal/cluster/provisioners/vsphere/provisioner.go index 10d21fce3..003efa57b 100644 --- a/internal/cluster/provisioners/vsphere/provisioner.go +++ b/internal/cluster/provisioners/vsphere/provisioner.go @@ -20,9 +20,10 @@ import ( getter "github.com/hashicorp/go-getter" "github.com/hashicorp/terraform-exec/tfexec" "github.com/relex/aini" + "github.com/sirupsen/logrus" + cfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sighupio/furyctl/internal/configuration" - "github.com/sirupsen/logrus" ) // VSphere represents the VSphere provisioner diff --git a/internal/configuration/config.go b/internal/configuration/config.go index eb8294a07..fcc2de275 100644 --- a/internal/configuration/config.go +++ b/internal/configuration/config.go @@ -9,11 +9,11 @@ import ( "fmt" "io/ioutil" - bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" + + bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" + clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" ) // TerraformExecutor represents the terraform executor configuration to be used diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go index bbb2e9006..eaa9ea4e1 100644 --- a/internal/configuration/templates.go +++ b/internal/configuration/templates.go @@ -8,11 +8,11 @@ package configuration import ( "fmt" - bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" + + bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" + clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" ) // Template generates a yaml with a sample configuration requested by the client diff --git a/internal/project/project.go b/internal/project/project.go index f8dc196e2..0f1b127cc 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -10,8 +10,9 @@ import ( "io/ioutil" "os" - "github.com/sighupio/furyctl/pkg/utils" "github.com/sirupsen/logrus" + + "github.com/sighupio/furyctl/pkg/utils" ) const ( diff --git a/internal/provisioners/provisioners.go b/internal/provisioners/provisioners.go index 9e11e89dc..bf526f1b2 100644 --- a/internal/provisioners/provisioners.go +++ b/internal/provisioners/provisioners.go @@ -11,13 +11,14 @@ import ( "github.com/gobuffalo/packr/v2" "github.com/hashicorp/terraform-exec/tfexec" + "github.com/sirupsen/logrus" + "github.com/sighupio/furyctl/internal/bootstrap/provisioners/aws" "github.com/sighupio/furyctl/internal/bootstrap/provisioners/gcp" "github.com/sighupio/furyctl/internal/cluster/provisioners/eks" "github.com/sighupio/furyctl/internal/cluster/provisioners/gke" "github.com/sighupio/furyctl/internal/cluster/provisioners/vsphere" "github.com/sighupio/furyctl/internal/configuration" - "github.com/sirupsen/logrus" ) // Provisioner represents a kubernetes terraform provisioner diff --git a/internal/template/funcmap_test.go b/internal/template/funcmap_test.go index cb3bd1fbe..d2b0171f7 100644 --- a/internal/template/funcmap_test.go +++ b/internal/template/funcmap_test.go @@ -7,8 +7,9 @@ package template_test import ( "testing" - "github.com/sighupio/furyctl/internal/template" "github.com/stretchr/testify/assert" + + "github.com/sighupio/furyctl/internal/template" ) func TestNewFuncMap(t *testing.T) { diff --git a/internal/template/generator.go b/internal/template/generator.go index 79fcf23a6..cee8ff6d2 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -13,8 +13,9 @@ import ( "strings" "text/template" - "github.com/sighupio/furyctl/internal/io" "github.com/sirupsen/logrus" + + "github.com/sighupio/furyctl/internal/io" ) var ( diff --git a/internal/template/mapper/mapper_test.go b/internal/template/mapper/mapper_test.go index 0a85a169b..db90ed1e6 100644 --- a/internal/template/mapper/mapper_test.go +++ b/internal/template/mapper/mapper_test.go @@ -9,8 +9,9 @@ import ( "os" "testing" - "github.com/sighupio/furyctl/internal/template/mapper" "github.com/stretchr/testify/assert" + + "github.com/sighupio/furyctl/internal/template/mapper" ) func TestNewMapper(t *testing.T) { diff --git a/internal/template/model.go b/internal/template/model.go index f12f53a36..dc2c83d11 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -12,12 +12,12 @@ import ( "regexp" "strings" + "gopkg.in/yaml.v2" + "github.com/sighupio/furyctl/internal/io" "github.com/sighupio/furyctl/internal/template/mapper" yaml2 "github.com/sighupio/furyctl/internal/yaml" fTemplate "github.com/sighupio/furyctl/pkg/template" - - "gopkg.in/yaml.v2" ) type Model struct { diff --git a/internal/template/model_test.go b/internal/template/model_test.go index 25ebac716..abe3ccf8b 100644 --- a/internal/template/model_test.go +++ b/internal/template/model_test.go @@ -8,9 +8,10 @@ import ( "os" "testing" - "github.com/sighupio/furyctl/internal/template" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" + + "github.com/sighupio/furyctl/internal/template" ) func TestNewTemplateModel(t *testing.T) { diff --git a/internal/template/node_test.go b/internal/template/node_test.go index dc638192b..ddd438e9c 100644 --- a/internal/template/node_test.go +++ b/internal/template/node_test.go @@ -10,8 +10,9 @@ import ( "text/template" "text/template/parse" - template2 "github.com/sighupio/furyctl/internal/template" "github.com/stretchr/testify/assert" + + template2 "github.com/sighupio/furyctl/internal/template" ) func TestNewNode(t *testing.T) { diff --git a/internal/yaml/yaml_test.go b/internal/yaml/yaml_test.go index 98970d123..67cef9090 100644 --- a/internal/yaml/yaml_test.go +++ b/internal/yaml/yaml_test.go @@ -8,9 +8,10 @@ import ( "os" "testing" - yaml "github.com/sighupio/furyctl/internal/yaml" "github.com/stretchr/testify/assert" yaml2 "gopkg.in/yaml.v2" + + yaml "github.com/sighupio/furyctl/internal/yaml" ) type TestYaml struct { diff --git a/main.go b/main.go index 053ed68c2..9eb6241c1 100644 --- a/main.go +++ b/main.go @@ -15,8 +15,9 @@ package main import ( - "github.com/sighupio/furyctl/cmd" "github.com/sirupsen/logrus" + + "github.com/sighupio/furyctl/cmd" ) var ( diff --git a/pkg/terraform/install.go b/pkg/terraform/install.go index fb4b2a97e..8f15f464d 100644 --- a/pkg/terraform/install.go +++ b/pkg/terraform/install.go @@ -15,8 +15,9 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-exec/tfexec" "github.com/hashicorp/terraform-exec/tfinstall" - "github.com/sighupio/furyctl/pkg/utils" "github.com/sirupsen/logrus" + + "github.com/sighupio/furyctl/pkg/utils" ) // ensure ensures a working terraform version to be used in the project From 90c101474f985727b9ea0285a0a955c262b61b44 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:30:33 +0200 Subject: [PATCH 021/383] chore: fix linting issues --- cmd/validate/dependencies.go | 4 ++-- pkg/analytics/analytics.go | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index c596d9eae..812164baa 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -26,9 +26,9 @@ var ( ErrSystemDepsValidation = errors.New("error while validating system dependencies") ErrEnvironmentDepsValidation = errors.New("error while validating environment dependencies") ErrEmptyToolVersion = errors.New("empty tool version") -) -var execCommand = exec.Command + execCommand = exec.Command +) func NewDependenciesCmd(version string) *cobra.Command { cmd := &cobra.Command{ diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index d43a548a5..06a30564f 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -32,20 +32,6 @@ var ( version string ) -func enabled() bool { - return !disable -} - -// Version will set the version of the CLI -func Version(v string) { - version = v -} - -// Disable will disable analytics -func Disable(d bool) { - disable = d -} - func init() { c := &http.Client{ Timeout: time.Second * 5, @@ -60,6 +46,20 @@ func init() { mixpanelClient = mixpanel.NewFromClient(c, mixpanelToken, "https://api-eu.mixpanel.com") } +func enabled() bool { + return !disable +} + +// Version will set the version of the CLI +func Version(v string) { + version = v +} + +// Disable will disable analytics +func Disable(d bool) { + disable = d +} + func track(event string, success bool, token string, props map[string]interface{}) { if enabled() { mpOS := "" From 30746564afb4b44dee0517dd47673d8fc5b567a3 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:31:06 +0200 Subject: [PATCH 022/383] feat: introduce new linting rules, new goreleaser rules, new makefile, improve ci --- .drone.yml | 62 ++--- .goreleaser.yml | 73 ++---- .rules/.golangci.yml | 566 +++++++++++++++++++++++++++++++++++++++++++ Makefile | 174 +++++++++---- go.mod | 4 +- 5 files changed, 737 insertions(+), 142 deletions(-) create mode 100644 .rules/.golangci.yml diff --git a/.drone.yml b/.drone.yml index 27c5339a2..ae0dfddba 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,19 +1,6 @@ # Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. - -name: license -kind: pipeline -type: docker - -steps: - - name: check - image: golang:1.18.1-alpine - pull: always - commands: - - go install github.com/google/addlicense@v1.0.0 - - addlicense -c "SIGHUP s.r.l" -y 2017-present -v -l bsd --check . - --- name: Build Test and Release kind: pipeline @@ -22,31 +9,27 @@ type: docker node: runner: internal -depends_on: - - license - steps: + - name: check + image: quay.io/sighup/golang:1.19.0 + pull: always + commands: + - go install github.com/google/addlicense@v1.0.0 + - make license-check + - name: lint - image: golang:1.18.1-alpine + image: quay.io/sighup/golang:1.19.0 pull: always commands: - - test -z $(gofmt -l .) - when: - event: - - push - - tag + - make lint - name: test - image: golang:1.18.1-alpine + image: quay.io/sighup/golang:1.19.0 pull: always commands: - - go test -v ./... + - make test environment: CGO_ENABLED: 0 - when: - event: - - push - - tag - name: build image: ghcr.io/goreleaser/goreleaser:v1.9.2 @@ -59,15 +42,9 @@ steps: GITHUB_TOKEN: from_secret: GITHUB_TOKEN commands: - - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags - - goreleaser check - - goreleaser --debug build --skip-validate --rm-dist - when: - event: - - push - - tag + - make build - &integration name: integration-gcp-gke @@ -80,10 +57,6 @@ steps: from_secret: FURYCTL_TOKEN commands: - bats -t ./automated-tests/integration/gcp-gke/tests.sh - when: - event: - - push - - tag - <<: *integration name: integration-aws-eks @@ -190,6 +163,7 @@ steps: depends_on: - lint - test + - build - integration-gcp-gke - integration-aws-eks - integration-vsphere @@ -203,12 +177,16 @@ steps: GITHUB_TOKEN: from_secret: GITHUB_TOKEN commands: - - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags - - goreleaser check - - goreleaser --debug release --rm-dist + - make release when: ref: include: - refs/tags/v** + +trigger: + event: + exclude: + - pull_request + - promote diff --git a/.goreleaser.yml b/.goreleaser.yml index 7d2f6dc55..e0a1d6461 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,60 +3,32 @@ # license that can be found in the LICENSE file. --- +project_name: furyctl before: hooks: - - packr2 build -project_name: furyctl + - go mod tidy builds: - - binary: 'furyctl' - id: furyctl-darwin - goarch: - - amd64 + - env: + - CGO_ENABLED=0 goos: + - linux + - windows - darwin - env: - - CGO_ENABLED=0 - ldflags: - - -s -w -X github.com/sighupio/furyctl/cmd.version={{.Version}} -X github.com/sighupio/furyctl/cmd.commit={{.Commit}} -X github.com/sighupio/furyctl/cmd.date={{.Date}} - - binary: 'furyctl' - id: furyctl-linux goarch: - amd64 - goos: - - linux - env: - - CGO_ENABLED=0 ldflags: - - -s -w -X github.com/sighupio/furyctl/cmd.version={{.Version}} -X github.com/sighupio/furyctl/cmd.commit={{.Commit}} -X github.com/sighupio/furyctl/cmd.date={{.Date}} + - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} archives: - - format: tar.gz - id: furyctl-darwin-tgz - wrap_in_directory: false - name_template: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}' - builds: - - furyctl-darwin - - format: tar.gz - id: furyctl-linux-tgz - wrap_in_directory: false - name_template: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}' - builds: - - furyctl-linux - - format: binary - id: furyctl-darwin-bin - wrap_in_directory: false - name_template: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}' - builds: - - furyctl-darwin - - format: binary - id: furyctl-linux-bin - wrap_in_directory: false - name_template: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}' - builds: - - furyctl-linux + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 checksum: - name_template: 'sha256sum.txt' + name_template: 'checksums.txt' snapshot: - name_template: '{{ .Tag }}' + name_template: "{{ incpatch .Version }}-next" changelog: sort: asc filters: @@ -64,11 +36,6 @@ changelog: - '^docs:' - '^test:' release: - ids: - - furyctl-linux-tgz - - furyctl-linux-bin - - furyctl-darwin-tgz - - furyctl-darwin-bin github: owner: sighupio name: furyctl @@ -89,6 +56,10 @@ brews: system "#{bin}/furyctl" install: | bin.install 'furyctl' - ids: - - furyctl-darwin-tgz - - furyctl-linux-tgz +dockers: + - skip_push: auto + image_templates: + - "sighupio/furyctl:latest" + - "sighupio/furyctl:v{{ .Major }}" + - "sighupio/furyctl:v{{ .Major }}.{{ .Minor }}" + - "sighupio/furyctl:{{ .Tag }}" diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml new file mode 100644 index 000000000..768bbd8da --- /dev/null +++ b/.rules/.golangci.yml @@ -0,0 +1,566 @@ +# This file contains all available configuration options +# with their default values (in comments). + +# Options for analysis running. +run: + timeout: 5m + skip-files: + - ".*\\.gen\\.go$" + modules-download-mode: readonly + +# output configuration options +output: + sort-results: true + +linters: + # Enable all available linters. + # Default: false + enable-all: true + # Disable specific linter + # https://golangci-lint.run/usage/linters/#disabled-by-default-linters--e--enable + disable: + # todo + - cyclop + - errcheck + - errorlint + - forcetypeassert + - funlen + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - godot + - godox + - goerr113 + - gomnd + - gosec + - gosimple + - govet + - ineffassign + - interfacebloat + - ireturn + - lll + - misspell + - nestif + - nilerr + - nlreturn + - nonamedreturns + - paralleltest + - revive + - staticcheck + - stylecheck + - tagliatelle + - tenv + - testpackage + - varnamelen + - whitespace + - wrapcheck + - wsl + # unsupported + - rowserrcheck + - sqlclosecheck + - wastedassign + # deprecated + - deadcode + - exhaustivestruct + - golint + - nosnakecase + - structcheck + - ifshort + - interfacer + - maligned + - scopelint + - varcheck + # unused + - exhaustruct + - forbidigo + +linters-settings: + decorder: + disable-init-func-first-check: false + errcheck: + check-type-assertions: true + check-blank: true + disable-default-exclusions: false + exclude-functions: [] + errchkjson: + check-error-free-encoding: false + report-no-exported: true + exhaustive: + check-generated: true + default-signifies-exhaustive: true + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/sighupio) # Custom section: groups all imports with the specified Prefix. + skip-generated: true + custom-order: true + godot: + scope: all + exclude: + - "^fixme:" + - "^todo:" + capital: true + period: true + gofumpt: + lang-version: "1.19" + extra-rules: true + goimports: + local-prefixes: github.com/sighupio + govet: + check-shadowing: true + grouper: + const-require-single-const: true + const-require-grouping: false + import-require-single-import: true + import-require-grouping: false + var-require-single-var: true + var-require-grouping: false + nolintlint: + require-explanation: true + require-specific: true + revive: + enable-all-rules: true + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant + - name: add-constant + severity: warning + disabled: false + arguments: + - maxLitCount: "3" + allowStrs: '""' + allowInts: "0,1,2" + allowFloats: "0.0,0.,1.0,1.,2.0,2." + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit + - name: argument-limit + severity: warning + disabled: false + arguments: [4] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic + - name: atomic + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#banned-characters + - name: banned-characters + severity: warning + disabled: false + arguments: ["Ω", "Σ", "σ", "7"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bare-return + - name: bare-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports + - name: blank-imports + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr + - name: bool-literal-in-expr + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#call-to-gc + - name: call-to-gc + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cognitive-complexity + - name: cognitive-complexity + severity: warning + disabled: false + arguments: [7] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming + - name: confusing-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results + - name: confusing-results + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr + - name: constant-logical-expr + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument + - name: context-as-argument + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type + - name: context-keys-type + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cyclomatic + - name: cyclomatic + severity: warning + disabled: false + arguments: [3] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace + - name: datarace + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit + - name: deep-exit + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer + - name: defer + severity: warning + disabled: false + arguments: + - ["call-chain", "loop"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports + - name: dot-imports + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports + - name: duplicated-imports + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return + - name: early-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block + - name: empty-block + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines + - name: empty-lines + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming + - name: error-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return + - name: error-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings + - name: error-strings + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf + - name: errorf + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported + - name: exported + severity: warning + disabled: false + arguments: + - "checkPrivateReceivers" + - "sayRepetitiveInsteadOfStutters" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header + - name: file-header + severity: warning + disabled: false + arguments: + - This is the text that must appear at the top of source files. + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter + - name: flag-parameter + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-result-limit + - name: function-result-limit + severity: warning + disabled: false + arguments: [2] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length + - name: function-length + severity: warning + disabled: false + arguments: [10, 0] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return + - name: get-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches + - name: identical-branches + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return + - name: if-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement + - name: increment-decrement + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow + - name: indent-error-flow + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#imports-blacklist + - name: imports-blacklist + severity: warning + disabled: false + arguments: + - "crypto/md5" + - "crypto/sha1" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing + - name: import-shadowing + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit + - name: line-length-limit + severity: warning + disabled: false + arguments: [120] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs + - name: max-public-structs + severity: warning + disabled: false + arguments: [3] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter + - name: modifies-parameter + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-value-receiver + - name: modifies-value-receiver + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#nested-structs + - name: nested-structs + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#optimize-operands-order + - name: optimize-operands-order + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments + - name: package-comments + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range + - name: range + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure + - name: range-val-in-closure + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address + - name: range-val-address + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming + - name: receiver-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id + - name: redefines-builtin-id + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-of-int + - name: string-of-int + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format + - name: string-format + severity: warning + disabled: false + arguments: + - - 'core.WriteError[1].Message' + - '/^([^A-Z]|$)/' + - must not start with a capital letter + - - 'fmt.Errorf[0]' + - '/(^|[^\.!?])$/' + - must not end in punctuation + - - panic + - '/^[^\n]*$/' + - must not contain line breaks + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag + - name: struct-tag + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else + - name: superfluous-else + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal + - name: time-equal + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-naming + - name: time-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming + - name: var-naming + severity: warning + disabled: false + arguments: + - ["ID"] # AllowList + - ["VM"] # DenyList + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration + - name: var-declaration + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion + - name: unconditional-recursion + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming + - name: unexported-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return + - name: unexported-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error + - name: unhandled-error + severity: warning + disabled: false + arguments: + - "fmt.Printf" + - "myFunction" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt + - name: unnecessary-stmt + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unreachable-code + - name: unreachable-code + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + - name: unused-parameter + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver + - name: unused-receiver + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break + - name: useless-break + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value + - name: waitgroup-by-value + severity: warning + disabled: false + varcheck: + exported-fields: true + varnamelen: + check-receiver: true + check-return: true + check-type-param: true + ignore-type-assert-ok: true + ignore-map-index-ok: true + ignore-chan-recv-ok: true + ignore-names: + - err + ignore-decls: + - c echo.Context + - t testing.T + - e error + - i int + - const C + - T any + - m map[string]int + wsl: + allow-cuddle-declarations: false + allow-multiline-assign: true + allow-separated-leading-comment: false + force-case-trailing-whitespace: 1 + force-err-cuddling: true +issues: + # List of regexps of issue texts to exclude. + # + # But independently of this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. + # To list all excluded by default patterns execute `golangci-lint run --help` + # + # Default: [] + exclude: [] + + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - gochecknoglobals + - cyclop + - dupl + - errcheck + - forcetypeassert + - funlen + - goconst + - gocyclo + - goerr113 + - gosec + - lll + - maintidx + - path: main\.go + linters: + - gochecknoglobals + + # Independently of option `exclude` we use default exclude patterns, + # it can be disabled by this option. + # To list all excluded by default patterns execute `golangci-lint run --help`. + # Default: true. + exclude-use-default: true + + # If set to true exclude and exclude-rules regular expressions become case-sensitive. + # Default: false + exclude-case-sensitive: false + + # The list of ids of default excludes to include or disable. + # Default: [] + include: [] + + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 50 + max-issues-per-linter: 50 + + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 3 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing large codebase. + # It's not practical to fix all existing issues at the moment of integration: + # much better don't allow issues in new code. + # + # Default: false. + new: false + + # Show only new issues created after git revision `REV`. + # new-from-rev: HEAD + + # Show only new issues created in git patch with set file path. + # new-from-patch: path/to/patch/file + + # Fix found issues (if it's supported by the linter). + fix: false + +severity: + # Set the default severity for issues. + # + # If severity rules are defined and the issues do not match or no severity is provided to the rule + # this will be the default severity applied. + # Severities should match the supported severity names of the selected out format. + # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity + # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity + # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + # + # Default value is an empty string. + default-severity: error + + # If set to true `severity-rules` regular expressions become case-sensitive. + # Default: false + case-sensitive: true + + # When a list of severity rules are provided, severity information will be added to lint issues. + # Severity rules have the same filtering capability as exclude rules + # except you are allowed to specify one matcher per severity rule. + # Only affects out formats that support setting severity information. + # + # Default: [] + rules: + - linters: + - dupl + severity: info diff --git a/Makefile b/Makefile index 891f1a1cd..e372d7cb8 100644 --- a/Makefile +++ b/Makefile @@ -1,58 +1,138 @@ -.DEFAULT_GOAL: help -SHELL := /bin/bash +_PROJECT_DIRECTORY = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) +_GOLANG_IMAGE = golang:1.19.1 +_PROJECTNAME = furyctl +_GOARCH = "amd64" -PROJECTNAME := $(shell basename "$(PWD)") -CURRENT_DIR := $(shell pwd) +NETRC_FILE ?= ~/.netrc -GOARCH = "amd64" ifeq ("$(shell uname -m)", "arm64") - GOARCH = "arm64" + _GOARCH = "arm64" endif -.PHONY: help -all: help -help: Makefile - @echo - @echo " Choose a command run in "$(PROJECTNAME)":" - @echo - @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' - @echo - -.PHONY: deps -## deps: download requires dependencies -deps: - @go get -u github.com/gobuffalo/packr/v2/packr2 - -.PHONY: -## policeman: Execute policeman -policeman: - @docker pull quay.io/sighup/policeman - @docker run --rm -v ${CURRENT_DIR}:/app -w /app quay.io/sighup/policeman - -.PHONY: lint -## lint: Execute linter. Can cause modifications -lint: - @gofmt -s -w . +#1: docker image +#2: make target +define run-docker + @docker run --rm \ + -e CGO_ENABLED=0 \ + -e GOARCH=${_GOARCH} \ + -e GOOS=linux \ + -w /app \ + -v ${NETRC_FILE}:/root/.netrc \ + -v ${_PROJECT_DIRECTORY}:/app \ + $(1) $(2) +endef + +.PHONY: env + +env: + @echo 'export CGO_ENABLED=0' + @echo 'export GOARCH=${_GOARCH}' + @grep -v '^#' .env | sed 's/^/export /' + +.PHONY: mod-download mod-tidy mod-verify + +mod-download: + @go mod download + +mod-tidy: + @go mod tidy + +mod-verify: + @go mod verify + +.PHONY: mod-check-upgrades mod-upgrade + +mod-check-upgrades: + @go list -mod=readonly -u -f "{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}" -m all + +mod-upgrade: + @go get -u ./... && go mod tidy + +.PHONY: generate license-add license-check + +license-add: + @addlicense -c "SIGHUP s.r.l" -y 2017-present -v -l bsd \ + -ignore "scripts/e2e/libs/**/*" \ + -ignore "vendor/**/*" \ + -ignore "*.gen.go" \ + -ignore ".idea/*" \ + -ignore ".vscode/*" \ + -ignore "*.js" \ + -ignore "kind-config.yaml" \ + -ignore ".husky/**/*" \ + -ignore ".go/**/*" \ + . + +license-check: + @addlicense -c "SIGHUP s.r.l" -y 2017-present -v -l bsd \ + -ignore "scripts/e2e/libs/**/*" \ + -ignore "vendor/**/*" \ + -ignore "*.gen.go" \ + -ignore ".idea/*" \ + -ignore ".vscode/*" \ + -ignore "*.js" \ + -ignore "kind-config.yaml" \ + -ignore ".husky/**/*" \ + -ignore ".go/**/*" \ + --check . + +.PHONY: fmt fumpt imports gci + +fmt: + @find . -name "*.go" -type f -not -path '*/vendor/*' \ + | sed 's/^\.\///g' \ + | xargs -I {} sh -c 'echo "formatting {}.." && gofmt -w -s {}' + +fumpt: + @find . -name "*.go" -type f -not -path '*/vendor/*' \ + | sed 's/^\.\///g' \ + | xargs -I {} sh -c 'echo "formatting {}.." && gofumpt -w -extra {}' + +imports: + @goimports -v -w -e -local github.com/sighupio main.go + @goimports -v -w -e -local github.com/sighupio cmd/ + @goimports -v -w -e -local github.com/sighupio internal/ + @goimports -v -w -e -local github.com/sighupio pkg/ + +gci: + @find . -name "*.go" -type f -not -path '*/vendor/*' \ + | sed 's/^\.\///g' \ + | xargs -I {} sh -c 'echo "formatting imports for {}.." && \ + gci write --skip-generated -s standard,default,"prefix(github.com/sighupio)" {}' + +.PHONY: lint lint-go + +lint: lint-go + +lint-go: + @golangci-lint -v run --color=always --config=${_PROJECT_DIRECTORY}/.rules/.golangci.yml ./... .PHONY: test -## test: Check the linter and unit tests results + test: - @test -z $(gofmt -l .) - @go test -v ./... + @go test -v -race -covermode=atomic -coverprofile=coverage.out ./... + +.PHONY: clean build release -.PHONY: clean -## clean: Removes temporary and build results clean: deps - @GO111MODULE=on packr2 clean - @rm -rf bin furyctl dist - @go mod tidy + @if [ -d bin ]; then rm -rf bin; fi + @if [ -d dist ]; then rm -rf dist; fi + @if [ -f furyctl ]; then rm furyctl; fi + +build: + @export GO_VERSION=$$(go version | cut -d ' ' -f 3) && \ + goreleaser check && \ + goreleaser release --debug --snapshot --rm-dist + +release: + @export GO_VERSION=$$(go version | cut -d ' ' -f 3) && \ + goreleaser check && \ + goreleaser --debug release --rm-dist + +# Helpers + +%-docker: + $(call run-docker,${_GOLANG_IMAGE},make $*) -.PHONY: build -## build: Builds the solution for linux and macos amd64 or arm64 -build: lint deps clean test - @GO111MODULE=on packr2 build - @GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} go build -a -ldflags '-extldflags "-static"' -o bin/linux-${GOARCH}/$(version)/furyctl . - @GO111MODULE=on CGO_ENABLED=0 GOOS=darwin GOARCH=${GOARCH} go build -a -ldflags '-extldflags "-static"' -o bin/darwin-${GOARCH}/$(version)/furyctl . - @mkdir -p bin/{darwin,linux}/latest - @cp bin/darwin-${GOARCH}/$(version)/furyctl bin/darwin/latest/furyctl - @cp bin/linux-${GOARCH}/$(version)/furyctl bin/linux/latest/furyctl +check-variable-%: # detection of undefined variables. + @[[ "${${*}}" ]] || (echo '*** Please define variable `${*}` ***' && exit 1) diff --git a/go.mod b/go.mod index c5367c124..a2b9c6762 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/sighupio/furyctl +go 1.19 + require ( github.com/briandowns/spinner v1.12.0 github.com/denisbrodbeck/machineid v1.0.1 @@ -98,5 +100,3 @@ require ( google.golang.org/protobuf v1.26.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect ) - -go 1.18 From 79db9fa9424edceede40576ed494a7e8622586b0 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:37:31 +0200 Subject: [PATCH 023/383] chore: rename drone pipeline --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index ae0dfddba..a07acf236 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,9 +2,9 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. --- -name: Build Test and Release kind: pipeline type: docker +name: main node: runner: internal From 44b15fe99e1b6a6cc6d51649084aef02042b7a90 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:38:16 +0200 Subject: [PATCH 024/383] chore: remove three dashes on top of drone manifest --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index a07acf236..727eecf55 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,7 +1,7 @@ # Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. ---- + kind: pipeline type: docker name: main From 31d17dcebdc071ef050bec9ac5537b0f1c930c5c Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:40:10 +0200 Subject: [PATCH 025/383] fix: add missing license banners --- .gitignore | 1 + .rules/.golangci.yml | 4 ++++ internal/cobrax/flags.go | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 7c161bedf..af8a69408 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +coverage.out vendor bin .idea diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml index 768bbd8da..d82510b2a 100644 --- a/.rules/.golangci.yml +++ b/.rules/.golangci.yml @@ -1,3 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + # This file contains all available configuration options # with their default values (in comments). diff --git a/internal/cobrax/flags.go b/internal/cobrax/flags.go index 4dadb9149..a24a32c26 100644 --- a/internal/cobrax/flags.go +++ b/internal/cobrax/flags.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package cobrax import ( From d394037b715a50e59b72fe33d6875f7733adaab9 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 18:40:55 +0200 Subject: [PATCH 026/383] chore: remove -race flag from tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e372d7cb8..0b96019a5 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,7 @@ lint-go: .PHONY: test test: - @go test -v -race -covermode=atomic -coverprofile=coverage.out ./... + @go test -v -covermode=atomic -coverprofile=coverage.out ./... .PHONY: clean build release From 5021b3d6e2311fbd75b80c5f249c8248d728c676 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:11:03 +0200 Subject: [PATCH 027/383] chore: cleanup providers, rename non-standard folders, move code to internal --- .drone.yml | 84 +-- .goreleaser.yml | 8 +- Furyfile.yml | 62 -- Makefile | 1 - README.md | 13 +- automated-tests/e2e-tests/gcp-gke/.gitignore | 4 - .../e2e-tests/gcp-gke/bootstrap.tpl.yml | 30 - .../e2e-tests/gcp-gke/cluster.tpl.yml | 48 -- automated-tests/e2e-tests/gcp-gke/tests.sh | 274 --------- automated-tests/e2e-tests/vsphere/.gitignore | 4 - .../e2e-tests/vsphere/cluster.tpl.yml | 52 -- .../e2e-tests/vsphere/tests-destroy.sh | 43 -- automated-tests/e2e-tests/vsphere/tests.sh | 138 ----- .../integration/gcp-gke/.gitignore | 2 - .../integration/gcp-gke/bootstrap.yml | 21 - .../integration/gcp-gke/cluster.yml | 29 - automated-tests/integration/gcp-gke/tests.sh | 93 --- .../complex-dry-run/target/kustomization.yaml | 19 - .../complex/source/config/example.yaml | 5 - .../complex/target/config/example.yaml | 5 - .../complex/target/kustomization.yaml | 18 - .../target/file.txt | 1 - .../test-data/simple-dry-run/target/file.txt | 1 - .../simple-dry-run/target/keepfile.txt | 1 - .../test-data/simple/source/keepfile.txt | 1 - .../test-data/simple/target/file.txt | 1 - .../test-data/simple/target/keepfile.txt | 1 - .../integration/vsphere/.gitignore | 1 - .../integration/vsphere/cluster.yml | 44 -- automated-tests/integration/vsphere/tests.sh | 69 --- cmd/root.go | 2 +- data/provisioners/bootstrap/gcp/main.tf | 43 -- data/provisioners/bootstrap/gcp/output.tf | 61 -- data/provisioners/bootstrap/gcp/variables.tf | 109 ---- data/provisioners/cluster/eks/variables.tf | 2 +- data/provisioners/cluster/gke/main.tf | 42 -- data/provisioners/cluster/gke/output.tf | 48 -- data/provisioners/cluster/gke/variables.tf | 116 ---- .../cluster/vsphere/furyagent/.gitattributes | 1 - .../cluster/vsphere/furyagent/furyagent.yml | 7 - data/provisioners/cluster/vsphere/main.tf | 102 ---- data/provisioners/cluster/vsphere/output.tf | 17 - .../cluster/vsphere/provision/all-in-one.yml | 116 ---- .../cluster/vsphere/provision/ansible.cfg | 10 - .../provisioners/cluster/vsphere/variables.tf | 278 --------- docs/assets/fury-epta-white.png | Bin 6026 -> 0 bytes docs/design/001-Cluster-Self-Provisioning.md | 146 ----- .../002-Cluster-Self-Provisioning - GKE.md | 132 ----- docs/design/003-Fury-vShpere-Provisioner.md | 143 ----- docs/upgrading_to_v0.5.1.md | 215 ------- go.mod | 1 - go.sum | 4 - {pkg => internal}/analytics/analytics.go | 0 internal/bootstrap/bootstrap.go | 2 +- internal/bootstrap/configuration/gcp.go | 37 -- .../bootstrap/provisioners/gcp/provisioner.go | 311 ---------- internal/cluster/cluster.go | 2 +- internal/cluster/configuration/gke.go | 53 -- internal/cluster/configuration/vsphere.go | 86 --- .../cluster/provisioners/gke/provisioner.go | 356 ----------- .../provisioners/vsphere/provisioner.go | 557 ------------------ internal/configuration/config.go | 42 -- internal/configuration/templates.go | 26 - internal/io/fs.go | 13 + internal/project/project.go | 4 +- internal/provisioners/provisioners.go | 9 - internal/template/funcmap.go | 20 + internal/template/model.go | 5 +- {pkg => internal}/terraform/install.go | 6 +- {pkg => internal}/terraform/terraform.go | 0 pkg/template/funcmap.go | 29 - pkg/utils/utils.go | 51 -- .../e2e-tests => test/e2e}/aws-eks/.gitignore | 0 .../e2e}/aws-eks/bootstrap.tpl.yml | 0 .../e2e}/aws-eks/cluster.tpl.yml | 0 .../e2e-tests => test/e2e}/aws-eks/tests.sh | 57 +- {automated-tests => test}/helper.bash | 0 .../integration/aws-eks/.gitignore | 0 .../integration/aws-eks/bootstrap.yml | 0 .../integration/aws-eks/cluster.yml | 0 .../integration/aws-eks/tests.sh | 8 +- .../integration/template-engine/.gitignore | 0 .../data/expected-kustomization.yaml | 0 .../complex-dry-run/distribution.yaml | 0 .../test-data/complex-dry-run/furyctl.yaml | 0 .../source/config/example.yaml | 0 .../source/kustomization.yaml.tpl | 0 .../complex/data/expected-kustomization.yaml | 0 .../test-data/complex/distribution.yaml | 0 .../test-data/complex/furyctl.yaml | 0 .../complex/source}/config/example.yaml | 0 .../complex/source/kustomization.yaml.tpl | 0 .../distribution.yaml | 0 .../furyctl.yaml | 0 .../source/file.txt.tpl | 0 .../source/keepfile.txt | 0 .../test-data/empty/source/file.txt.tpl | 0 .../test-data/no-distribution-yaml/.gitkeep | 0 .../no-furyctl-yaml/distribution.yaml | 0 .../simple-dry-run/distribution.yaml | 0 .../test-data/simple-dry-run/furyctl.yaml | 0 .../simple-dry-run/source/file.txt.tpl | 0 .../simple-dry-run/source}/keepfile.txt | 0 .../test-data/simple/distribution.yaml | 0 .../test-data/simple/furyctl.yaml | 0 .../test-data/simple/source/file.txt.tpl | 0 .../test-data/simple}/source/keepfile.txt | 0 .../integration/template-engine/tests.sh | 16 +- .../furyctl-defaults.yaml | 0 .../config-invalid-furyctl-yaml/furyctl.yaml | 0 .../config-invalid-furyctl-yaml/kfd.yaml | 0 .../schemas/ekscluster-kfd-v1alpha2.json | 0 .../furyctl-defaults.yaml | 0 .../config-valid-furyctl-yaml/furyctl.yaml | 0 .../config-valid-furyctl-yaml/kfd.yaml | 0 .../schemas/ekscluster-kfd-v1alpha2.json | 0 .../test-data/dependencies-correct/ansible | 0 .../test-data/dependencies-correct/furyagent | 0 .../dependencies-correct/furyctl.yaml | 0 .../test-data/dependencies-correct/kfd.yaml | 0 .../test-data/dependencies-correct/kubectl | 0 .../test-data/dependencies-correct/kustomize | 0 .../test-data/dependencies-correct/terraform | 0 .../dependencies-missing/furyctl.yaml | 0 .../test-data/dependencies-missing/kfd.yaml | 0 .../test-data/dependencies-wrong/ansible | 0 .../test-data/dependencies-wrong/furyagent | 0 .../test-data/dependencies-wrong/furyctl.yaml | 0 .../test-data/dependencies-wrong/kfd.yaml | 0 .../test-data/dependencies-wrong/kubectl | 0 .../test-data/dependencies-wrong/kustomize | 0 .../test-data/dependencies-wrong/terraform | 0 .../integration/validation-cmd/tests.sh | 10 +- 133 files changed, 103 insertions(+), 4265 deletions(-) delete mode 100644 Furyfile.yml delete mode 100644 automated-tests/e2e-tests/gcp-gke/.gitignore delete mode 100644 automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml delete mode 100644 automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml delete mode 100644 automated-tests/e2e-tests/gcp-gke/tests.sh delete mode 100644 automated-tests/e2e-tests/vsphere/.gitignore delete mode 100644 automated-tests/e2e-tests/vsphere/cluster.tpl.yml delete mode 100644 automated-tests/e2e-tests/vsphere/tests-destroy.sh delete mode 100644 automated-tests/e2e-tests/vsphere/tests.sh delete mode 100644 automated-tests/integration/gcp-gke/.gitignore delete mode 100644 automated-tests/integration/gcp-gke/bootstrap.yml delete mode 100644 automated-tests/integration/gcp-gke/cluster.yml delete mode 100644 automated-tests/integration/gcp-gke/tests.sh delete mode 100644 automated-tests/integration/template-engine/test-data/complex-dry-run/target/kustomization.yaml delete mode 100644 automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml delete mode 100644 automated-tests/integration/template-engine/test-data/complex/target/config/example.yaml delete mode 100644 automated-tests/integration/template-engine/test-data/complex/target/kustomization.yaml delete mode 100644 automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target/file.txt delete mode 100644 automated-tests/integration/template-engine/test-data/simple-dry-run/target/file.txt delete mode 100644 automated-tests/integration/template-engine/test-data/simple-dry-run/target/keepfile.txt delete mode 100644 automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt delete mode 100644 automated-tests/integration/template-engine/test-data/simple/target/file.txt delete mode 100644 automated-tests/integration/template-engine/test-data/simple/target/keepfile.txt delete mode 100644 automated-tests/integration/vsphere/.gitignore delete mode 100644 automated-tests/integration/vsphere/cluster.yml delete mode 100644 automated-tests/integration/vsphere/tests.sh delete mode 100644 data/provisioners/bootstrap/gcp/main.tf delete mode 100644 data/provisioners/bootstrap/gcp/output.tf delete mode 100644 data/provisioners/bootstrap/gcp/variables.tf delete mode 100644 data/provisioners/cluster/gke/main.tf delete mode 100644 data/provisioners/cluster/gke/output.tf delete mode 100644 data/provisioners/cluster/gke/variables.tf delete mode 100644 data/provisioners/cluster/vsphere/furyagent/.gitattributes delete mode 100644 data/provisioners/cluster/vsphere/furyagent/furyagent.yml delete mode 100644 data/provisioners/cluster/vsphere/main.tf delete mode 100644 data/provisioners/cluster/vsphere/output.tf delete mode 100644 data/provisioners/cluster/vsphere/provision/all-in-one.yml delete mode 100644 data/provisioners/cluster/vsphere/provision/ansible.cfg delete mode 100644 data/provisioners/cluster/vsphere/variables.tf delete mode 100644 docs/assets/fury-epta-white.png delete mode 100644 docs/design/001-Cluster-Self-Provisioning.md delete mode 100644 docs/design/002-Cluster-Self-Provisioning - GKE.md delete mode 100644 docs/design/003-Fury-vShpere-Provisioner.md delete mode 100644 docs/upgrading_to_v0.5.1.md rename {pkg => internal}/analytics/analytics.go (100%) delete mode 100644 internal/bootstrap/configuration/gcp.go delete mode 100644 internal/bootstrap/provisioners/gcp/provisioner.go delete mode 100644 internal/cluster/configuration/gke.go delete mode 100644 internal/cluster/configuration/vsphere.go delete mode 100644 internal/cluster/provisioners/gke/provisioner.go delete mode 100644 internal/cluster/provisioners/vsphere/provisioner.go rename {pkg => internal}/terraform/install.go (95%) rename {pkg => internal}/terraform/terraform.go (100%) delete mode 100644 pkg/template/funcmap.go delete mode 100644 pkg/utils/utils.go rename {automated-tests/e2e-tests => test/e2e}/aws-eks/.gitignore (100%) rename {automated-tests/e2e-tests => test/e2e}/aws-eks/bootstrap.tpl.yml (100%) rename {automated-tests/e2e-tests => test/e2e}/aws-eks/cluster.tpl.yml (100%) rename {automated-tests/e2e-tests => test/e2e}/aws-eks/tests.sh (64%) rename {automated-tests => test}/helper.bash (100%) rename {automated-tests => test}/integration/aws-eks/.gitignore (100%) rename {automated-tests => test}/integration/aws-eks/bootstrap.yml (100%) rename {automated-tests => test}/integration/aws-eks/cluster.yml (100%) rename {automated-tests => test}/integration/aws-eks/tests.sh (84%) rename {automated-tests => test}/integration/template-engine/.gitignore (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex-dry-run/distribution.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex-dry-run/furyctl.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex/data/expected-kustomization.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex/distribution.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex/furyctl.yaml (100%) rename {automated-tests/integration/template-engine/test-data/complex-dry-run/target => test/integration/template-engine/test-data/complex/source}/config/example.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl (100%) rename {automated-tests => test}/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl (100%) rename {automated-tests => test}/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt (100%) rename {automated-tests => test}/integration/template-engine/test-data/empty/source/file.txt.tpl (100%) rename {automated-tests => test}/integration/template-engine/test-data/no-distribution-yaml/.gitkeep (100%) rename {automated-tests => test}/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/simple-dry-run/distribution.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/simple-dry-run/furyctl.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl (100%) rename {automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target => test/integration/template-engine/test-data/simple-dry-run/source}/keepfile.txt (100%) rename {automated-tests => test}/integration/template-engine/test-data/simple/distribution.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/simple/furyctl.yaml (100%) rename {automated-tests => test}/integration/template-engine/test-data/simple/source/file.txt.tpl (100%) rename {automated-tests/integration/template-engine/test-data/simple-dry-run => test/integration/template-engine/test-data/simple}/source/keepfile.txt (100%) rename {automated-tests => test}/integration/template-engine/tests.sh (89%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/ansible (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/furyagent (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/kubectl (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/kustomize (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-correct/terraform (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/ansible (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/furyagent (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/kubectl (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/kustomize (100%) rename {automated-tests => test}/integration/validation-cmd/test-data/dependencies-wrong/terraform (100%) rename {automated-tests => test}/integration/validation-cmd/tests.sh (90%) diff --git a/.drone.yml b/.drone.yml index 727eecf55..1abdbe212 100644 --- a/.drone.yml +++ b/.drone.yml @@ -42,13 +42,14 @@ steps: GITHUB_TOKEN: from_secret: GITHUB_TOKEN commands: + - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags - make build - &integration - name: integration-gcp-gke - image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.3.0_v1.24.9_20.04 + name: integration-aws-eks + image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 depends_on: - build environment: @@ -56,31 +57,20 @@ steps: FURYCTL_TOKEN: from_secret: FURYCTL_TOKEN commands: - - bats -t ./automated-tests/integration/gcp-gke/tests.sh - - - <<: *integration - name: integration-aws-eks - commands: - - bats -t ./automated-tests/integration/aws-eks/tests.sh - - - <<: *integration - name: integration-vsphere - commands: - - bats -t ./automated-tests/integration/vsphere/tests.sh + - bats -t ./test/integration/aws-eks/tests.sh - <<: *integration name: integration-template-engine commands: - - bats -t ./automated-tests/integration/template-engine/tests.sh + - bats -t ./test/integration/template-engine/tests.sh - <<: *integration name: integration-validation-cmd commands: - - bats -t ./automated-tests/integration/validation-cmd/tests.sh + - bats -t ./test/integration/validation-cmd/tests.sh - - &e2e - name: e2e-gcp - image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.3.0_v1.24.9_20.04 + - name: e2e-aws + image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 pull: always privileged: true # Required to connect to the VPN depends_on: @@ -100,63 +90,12 @@ steps: from_secret: AWS_REGION AWS_DEFAULT_REGION: from_secret: AWS_REGION - - GCP_CREDENTIALS: - from_secret: GCP_CREDENTIALS - GCP_CREDENTIALS_PATH: /tmp/terraform-credentials.json - - GOOGLE_PROJECT: - from_secret: GOOGLE_PROJECT - CLOUDSDK_CORE_PROJECT: - from_secret: GOOGLE_PROJECT - GOOGLE_REGION: - from_secret: GOOGLE_REGION - - VSPHERE_USER: - from_secret: VSPHERE_USER - VSPHERE_PASSWORD: - from_secret: VSPHERE_PASSWORD - VSPHERE_SERVER: - from_secret: VSPHERE_SERVER - VSPHERE_DATACENTER: - from_secret: VSPHERE_DATACENTER - VSPHERE_DATASTORE: - from_secret: VSPHERE_DATASTORE - VSPHERE_HOST: - from_secret: VSPHERE_HOST - VSPHERE_NET: - from_secret: VSPHERE_NET - VSPHERE_TEMPLATE_PREFIX: - from_secret: VSPHERE_TEMPLATE_PREFIX commands: - - echo $${GCP_CREDENTIALS} | base64 -d > $${GCP_CREDENTIALS_PATH} - - export GOOGLE_APPLICATION_CREDENTIALS=$${GCP_CREDENTIALS_PATH} - - bats -t ./automated-tests/e2e-tests/gcp-gke/tests.sh + - bats -t ./test/e2e/aws-eks/tests.sh when: event: - tag - - <<: *e2e - name: e2e-aws - commands: - - bats -t ./automated-tests/e2e-tests/aws-eks/tests.sh - - - <<: *e2e - name: e2e-vsphere - commands: - - bats -t ./automated-tests/e2e-tests/vsphere/tests.sh - - - <<: *e2e - name: e2e-vsphere-destroy - depends_on: - - e2e-vsphere - failure: ignore - commands: - - echo " Our vsphere environment is not so stable" - - echo " The destroy phase fails randomly because of disconnection events between vsphere nodes and the API" - - echo " Ignore errors on this step" - - bats -t ./automated-tests/e2e-tests/vsphere/tests-destroy.sh - - name: build-release image: ghcr.io/goreleaser/goreleaser:v1.9.2 pull: always @@ -164,19 +103,16 @@ steps: - lint - test - build - - integration-gcp-gke - integration-aws-eks - - integration-vsphere - integration-template-engine - integration-validation-cmd - e2e-aws - - e2e-gcp - - e2e-vsphere environment: CGO_ENABLED: 0 GITHUB_TOKEN: from_secret: GITHUB_TOKEN commands: + - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags - make release diff --git a/.goreleaser.yml b/.goreleaser.yml index e0a1d6461..75a9e987a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,6 +7,7 @@ project_name: furyctl before: hooks: - go mod tidy + - packr2 build builds: - env: - CGO_ENABLED=0 @@ -56,10 +57,3 @@ brews: system "#{bin}/furyctl" install: | bin.install 'furyctl' -dockers: - - skip_push: auto - image_templates: - - "sighupio/furyctl:latest" - - "sighupio/furyctl:v{{ .Major }}" - - "sighupio/furyctl:v{{ .Major }}.{{ .Minor }}" - - "sighupio/furyctl:{{ .Tag }}" diff --git a/Furyfile.yml b/Furyfile.yml deleted file mode 100644 index bfda94c5c..000000000 --- a/Furyfile.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -versions: - dr: v1.10.1 - monitoring: v2.0.1 - on-premises: v1.24.7 - -roles: - - name: on-premises - -modules: - - name: dr/aws-velero - - name: terraform-aws-acm - version: v2.5.0 - provider: - name: aws - label: official-modules - registry: true - - name: tf_aws_bastion_s3_keys - version: v2.0.0 - provider: - name: aws - label: community-modules - registry: true - - name: terraform-google-kubernetes-engine - version: v6.2.0 - provider: - name: gcp - label: official-modules - registry: true - - name: terraform-azurerm-aks - version: v2.0 - provider: - name: azure - label: official-modules - registry: true - -bases: - - name: monitoring - - name: logging - version: main - - name: dr/velero/velero-base - - name: dr/velero/velero-aws - - name: dr/velero/velero-restic - -provider: - roles: {} - bases: {} - modules: - aws: - - url: https://github.com/terraform-aws-modules - label: official-modules - - url: https://github.com/terraform-community-modules - label: community-modules - gcp: - - url: https://github.com/terraform-google-modules - label: official-modules - azure: - - url: https://github.com/Azure - label: official-modules diff --git a/Makefile b/Makefile index 0b96019a5..4b476e2f1 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,6 @@ imports: @goimports -v -w -e -local github.com/sighupio main.go @goimports -v -w -e -local github.com/sighupio cmd/ @goimports -v -w -e -local github.com/sighupio internal/ - @goimports -v -w -e -local github.com/sighupio pkg/ gci: @find . -name "*.go" -type f -not -path '*/vendor/*' \ diff --git a/README.md b/README.md index d7a8c0d67..1f0a5ca1b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Furyctl is a simple CLI tool to: - download and manage the Kubernetes Fury Distribution (KFD) modules -- create and manage Fury clusters on AWS, GCP, and vSphere +- create and manage Fury clusters on AWS
@@ -250,13 +250,9 @@ The following workflow describes a setup of a cluster using an already existing +--------------------------+ +--------------------------+ ``` -### Provisioners +### Installers -To deploy all the infrastructure components `furyctl` uses *provisioners*. -Provisioners are Terraform projects directly integrated with the `furyctl` binary. Provisioners can be open-source (e.g. cluster EKS provisioner) or enterprise only (e.g. vSphere provisioner). - -To use an **enterprise** provisioner, you need to specify a token in the -`furyctl {bootstrap,cluster} {init,apply,destroy} --token YOUR_TOKEN` commands. +To deploy all the infrastructure components `furyctl` uses *installers*. > You can use an environment variable to avoid passing the token via console: `FURYCTL_TOKEN`. @@ -269,7 +265,6 @@ The available `bootstrap` provisioners are: | Provisioner | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `aws` | It creates a VPC with all the requirements to deploy a Kubernetes Cluster. It also includes a VPN instance easily manageable by using `furyagent`. | -| `gcp` | It creates a Network with all the requirements to deploy a Kubernetes Cluster. It also includes a VPN instance easily manageable by using `furyagent`. | #### Clusters @@ -278,8 +273,6 @@ The available `cluster` provisioners are: | Provisioner | Description | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `eks` | Creates an EKS cluster on an already existing VPC. It uses the [fury-eks-installer](https://github.com/sighupio/fury-eks-installer) | -| `gke` | Creates an GKE cluster on an already existing Network. It uses the [fury-gke-installer](https://github.com/sighupio/fury-gke-installer). | -| `vsphere` | **(enterprise)**: Creates a Kubernetes cluster on an already existing vSphere cluster. | diff --git a/automated-tests/e2e-tests/gcp-gke/.gitignore b/automated-tests/e2e-tests/gcp-gke/.gitignore deleted file mode 100644 index 242296826..000000000 --- a/automated-tests/e2e-tests/gcp-gke/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -cluster.yml -bootstrap.yml -cluster -bootstrap \ No newline at end of file diff --git a/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml b/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml deleted file mode 100644 index a87c11560..000000000 --- a/automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: e2e-${CI_BUILD_NUMBER} -spec: - publicSubnetsCIDRs: - - 10.0.1.0/24 - privateSubnetsCIDRs: - - 10.0.101.0/24 - clusterNetwork: - subnetworkCIDR: 10.1.0.0/16 - podSubnetworkCIDR: 10.2.0.0/16 - serviceSubnetworkCIDR: 10.3.0.0/16 - vpn: - subnetCIDR: 192.168.200.0/24 - sshUsers: - - jnardiello - region: europe-west1 - project: sighup-main -provisioner: gcp -executor: - state: - backend: s3 - config: - bucket: ${TERRAFORM_TF_STATES_BUCKET_NAME} - key: ${CI_REPO}/${DRONE_BRANCH}/${CI_BUILD_NUMBER}/bootstrap/gcp.state - region: ${AWS_REGION} diff --git a/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml b/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml deleted file mode 100644 index a878fb6b5..000000000 --- a/automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: e2e-${CI_BUILD_NUMBER} -spec: - region: ${GOOGLE_REGION} - project: ${GOOGLE_PROJECT} - version: 1.25 - network: e2e-${CI_BUILD_NUMBER} - subnetworks: - - e2e-${CI_BUILD_NUMBER}-cluster-subnet - - e2e-${CI_BUILD_NUMBER}-cluster-pod-subnet - - e2e-${CI_BUILD_NUMBER}-cluster-service-subnet - dmzCIDRRange: 10.0.0.0/16 - sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCjjHeHnfhplyak6p+HaDnl7Vz8knFjUfgpvtx2FzfrzVmNWh7EuBWrisYeh/vwCFvluOQtt5/J56Gu+N5q70XAEeuh1COeeYlRm0EHZtm0dAM7PCvZ4Ga20PYWGJAGWiKo3g+jh2AexEXw+t6O9qvTy1G2OQ7uOGBfu+fa4tpBpGpHI0IrdwVJ6m1sd08ghmyjvWeIlxOwIF2SCcQqFosUngrvVieemEeojRRc7sedqUrehLEOX8udF+vLV8cRvzUMqrpmyLnEBRtcFzOhKMKiE+xlk9IKKWnMXYDhXlj4AFDQ19Yii2Z9uRUMVr/YVpDNvR7lBZo+EvRg0w5w9u9 - nodePools: - - name: my-node-pool - os: COS_CONTAINERD - minSize: 1 - maxSize: 1 - volumeSize: 50 - instanceType: n1-standard-1 - additionalFirewallRules: - - name: dns - direction: ingress - cidrBlock: 0.0.0.0/0 - protocol: UDP - ports: 53-53 - tags: - allow: dns - - name: my-preemptible-pool - os: COS_CONTAINERD - minSize: 1 - maxSize: 1 - volumeSize: 50 - instanceType: n1-standard-1 - spotInstance: true -provisioner: gke -executor: - state: - backend: s3 - config: - bucket: ${TERRAFORM_TF_STATES_BUCKET_NAME} - key: ${CI_REPO}/${DRONE_BRANCH}/${CI_BUILD_NUMBER}/cluster/gke.state - region: ${AWS_REGION} diff --git a/automated-tests/e2e-tests/gcp-gke/tests.sh b/automated-tests/e2e-tests/gcp-gke/tests.sh deleted file mode 100644 index faa6615ea..000000000 --- a/automated-tests/e2e-tests/gcp-gke/tests.sh +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -load "./../../helper" - -OS="linux" -if [[ "$OSTYPE" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -@test "furyctl" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty version - } - run init - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Prepare bootstrap.yml file" { - info - init(){ - envsubst < ./automated-tests/e2e-tests/gcp-gke/bootstrap.tpl.yml > ./automated-tests/e2e-tests/gcp-gke/bootstrap.yml - } - run init - [ "$status" -eq 0 ] -} - -@test "Bootstrap init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./automated-tests/e2e-tests/gcp-gke/bootstrap.yml -w ./automated-tests/e2e-tests/gcp-gke/bootstrap --reset - } - run init - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Bootstrap apply (dry-run)" { - info - apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --dry-run --config ./automated-tests/e2e-tests/gcp-gke/bootstrap.yml -w ./automated-tests/e2e-tests/gcp-gke/bootstrap - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/bootstrap/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Bootstrap apply" { - info - apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --config ./automated-tests/e2e-tests/gcp-gke/bootstrap.yml -w ./automated-tests/e2e-tests/gcp-gke/bootstrap - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/bootstrap/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Create openvpn profile" { - info - apply(){ - furyagent configure openvpn-client --client-name e2e-"${CI_BUILD_NUMBER}" --config ./automated-tests/e2e-tests/gcp-gke/bootstrap/secrets/furyagent.yml > /tmp/e2e.ovpn - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Wait for openvpn instance SSH port open" { - info - check(){ - instance_ip=$(jq -r .vpn_ip.value[0] ./automated-tests/e2e-tests/gcp-gke/bootstrap/output/output.json) - echo " VPN Public IP: $instance_ip" >&3 - wait-for -t 60 "$instance_ip:22" -- echo "VPN Instance $instance_ip SSH Port (22) UP!" - } - run check - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Connect to the vpn" { - info - apply(){ - vpn-connect /tmp/e2e.ovpn - } - vpntest(){ - tuns=$(netstat -i | grep -c tun0) - if [ "$tuns" -eq 0 ]; then echo "VPN Connection not ready yet"; return 1; fi - } - run apply - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo "OVPN Profile: " >&3 - cat /tmp/e2e.ovpn >&3 - fi - [ "$status" -eq 0 ] - loop_it vpntest 60 5 - [ "$status" -eq 0 ] -} - -@test "Test Ping" { - info - check(){ - public_cidr=$(jq -r .public_subnets_cidr_blocks.value[0] ./automated-tests/e2e-tests/gcp-gke/bootstrap/output/output.json) - echo " Public CIDR: $public_cidr" >&3 - ips=$(nmap "$public_cidr" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b") - for ip in $ips; do - echo " Public (internal) ip discovered: $ip" >&3 - timeout 3 ping -c1 "$ip" - done - } - run check - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Prepare cluster.yml file" { - info - init(){ - envsubst < ./automated-tests/e2e-tests/gcp-gke/cluster.tpl.yml > ./automated-tests/e2e-tests/gcp-gke/cluster.yml - } - run init - [ "$status" -eq 0 ] -} - -@test "Cluster init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./automated-tests/e2e-tests/gcp-gke/cluster.yml -w ./automated-tests/e2e-tests/gcp-gke/cluster --reset - } - run init - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster apply (dry-run)" { - info - apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --dry-run --config ./automated-tests/e2e-tests/gcp-gke/cluster.yml -w ./automated-tests/e2e-tests/gcp-gke/cluster - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster apply" { - info - apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --config ./automated-tests/e2e-tests/gcp-gke/cluster.yml -w ./automated-tests/e2e-tests/gcp-gke/cluster - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "kubectl" { - info - cluster_info(){ - export KUBECONFIG=./automated-tests/e2e-tests/gcp-gke/cluster/secrets/kubeconfig - kubectl get pods -A >&3 - kubectl get nodes -o wide >&3 - } - run cluster_info - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} - -@test "kubectl get nodes verify spot presence" { - info - test(){ - export KUBECONFIG=./automated-tests/e2e-tests/gcp-gke/cluster/secrets/kubeconfig - data=$(kubectl get nodes --show-labels | grep "cloud.google.com/gke-preemptible=true") - if [ "${data}" == "" ]; then return 1; fi - } - loop_it test 60 5 - status=${loop_it_result} - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster destroy" { - info - destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster destroy --force --config ./automated-tests/e2e-tests/gcp-gke/cluster.yml -w ./automated-tests/e2e-tests/gcp-gke/cluster - } - run destroy - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Disconnect from the vpn" { - info - apply(){ - vpn-disconnect - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Bootstrap destroy" { - info - destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap destroy --force --config ./automated-tests/e2e-tests/gcp-gke/bootstrap.yml -w ./automated-tests/e2e-tests/gcp-gke/bootstrap - } - run destroy - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/gcp-gke/bootstrap/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} diff --git a/automated-tests/e2e-tests/vsphere/.gitignore b/automated-tests/e2e-tests/vsphere/.gitignore deleted file mode 100644 index 1962b26d0..000000000 --- a/automated-tests/e2e-tests/vsphere/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -cluster.yml -cluster -sshkey -sshkey.pub \ No newline at end of file diff --git a/automated-tests/e2e-tests/vsphere/cluster.tpl.yml b/automated-tests/e2e-tests/vsphere/cluster.tpl.yml deleted file mode 100644 index 9fbc61fc0..000000000 --- a/automated-tests/e2e-tests/vsphere/cluster.tpl.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: e2e-${CI_BUILD_NUMBER} -spec: - version: "1.20.5" - environmentName: "drone" - config: - datacenterName: "${VSPHERE_DATACENTER}" - datastore: "${VSPHERE_DATASTORE}" - esxiHosts: - - "${VSPHERE_HOST}" - networkConfig: - name: "${VSPHERE_NET}" - nameservers: - - 1.1.1.1 - - 8.8.8.8 - domain: localdomain - ipOffset: 1818 - lbNode: - count: 1 - template: "${VSPHERE_TEMPLATE_PREFIX}/sighup-ubuntu20LTS-template-v20210115" - masterNode: - count: 1 - cpu: 2 - memSize: 8192 - diskSize: 100 - template: "${VSPHERE_TEMPLATE_PREFIX}/sighup-ubuntu20LTS-template-v20210115" - infraNode: - count: 1 - cpu: 2 - memSize: 8192 - diskSize: 100 - template: "${VSPHERE_TEMPLATE_PREFIX}/sighup-ubuntu20LTS-template-v20210115" - nodePools: [] - clusterPODCIDR: 172.21.0.0/16 - clusterSVCCIDR: 172.23.0.0/16 - clusterCIDR: 10.4.0.0/16 - sshPublicKeys: - - /tmp/sshkey.pub -provisioner: vsphere -executor: - version: 0.13.6 - state: - backend: s3 - config: - bucket: ${TERRAFORM_TF_STATES_BUCKET_NAME} - key: ${CI_REPO}/${DRONE_BRANCH}/${CI_BUILD_NUMBER}/cluster/vsphere.state - region: ${AWS_REGION} diff --git a/automated-tests/e2e-tests/vsphere/tests-destroy.sh b/automated-tests/e2e-tests/vsphere/tests-destroy.sh deleted file mode 100644 index 87c972700..000000000 --- a/automated-tests/e2e-tests/vsphere/tests-destroy.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -load "./../../helper" - -OS="linux" -if [[ "$OSTYPE" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -@test "Prepare temporary ssh key" { - info - ssh_keys(){ - cp ./automated-tests/e2e-tests/vsphere/sshkey.pub /tmp/sshkey.pub - } - run ssh_keys - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster destroy" { - info - destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster destroy --force --config ./automated-tests/e2e-tests/vsphere/cluster.yml -w ./automated-tests/e2e-tests/vsphere/cluster - } - run destroy - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} diff --git a/automated-tests/e2e-tests/vsphere/tests.sh b/automated-tests/e2e-tests/vsphere/tests.sh deleted file mode 100644 index 9ef201a05..000000000 --- a/automated-tests/e2e-tests/vsphere/tests.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -load "./../../helper" - -OS="linux" -if [[ "$OSTYPE" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -@test "Prepare temporary ssh key" { - info - ssh_keys(){ - if [ -f ./automated-tests/e2e-tests/vsphere/sshkey ]; then - rm ./automated-tests/e2e-tests/vsphere/sshkey - fi - ssh-keygen -b 2048 -t rsa -f ./automated-tests/e2e-tests/vsphere/sshkey -q -N "" - cp ./automated-tests/e2e-tests/vsphere/sshkey.pub /tmp/sshkey.pub - } - run ssh_keys - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] - -} - -@test "furyctl" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty version - } - run init - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Prepare cluster.yml file" { - info - init(){ - envsubst < ./automated-tests/e2e-tests/vsphere/cluster.tpl.yml > ./automated-tests/e2e-tests/vsphere/cluster.yml - } - run init - [ "$status" -eq 0 ] -} - -@test "Cluster init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init \ - --config ./automated-tests/e2e-tests/vsphere/cluster.yml \ - -w ./automated-tests/e2e-tests/vsphere/cluster \ - --reset - } - run init - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster apply (dry-run)" { - info - apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --dry-run \ - --config ./automated-tests/e2e-tests/vsphere/cluster.yml \ - -w ./automated-tests/e2e-tests/vsphere/cluster - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/logs/terraform.logs >&3 - echo " ANSIBLE LOGS:" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/logs/ansible.log >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster apply" { - info - apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply \ - --config ./automated-tests/e2e-tests/vsphere/cluster.yml \ - -w ./automated-tests/e2e-tests/vsphere/cluster - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/logs/terraform.logs >&3 - echo " ANSIBLE LOGS:" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/logs/ansible.log >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Ping" { - info - ping(){ - cd ./automated-tests/e2e-tests/vsphere/cluster/provision && ansible all -m ping - } - run ping - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} - -@test "kubectl" { - info - cluster_info(){ - export KUBECONFIG=./automated-tests/e2e-tests/vsphere/cluster/secrets/kubeconfig - kubectl get pods -A >&3 - kubectl get nodes -o wide >&3 - } - run cluster_info - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./automated-tests/e2e-tests/vsphere/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} diff --git a/automated-tests/integration/gcp-gke/.gitignore b/automated-tests/integration/gcp-gke/.gitignore deleted file mode 100644 index dd6c13d71..000000000 --- a/automated-tests/integration/gcp-gke/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -cluster -bootstrap \ No newline at end of file diff --git a/automated-tests/integration/gcp-gke/bootstrap.yml b/automated-tests/integration/gcp-gke/bootstrap.yml deleted file mode 100644 index af0a7b7c7..000000000 --- a/automated-tests/integration/gcp-gke/bootstrap.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: furyctl -provisioner: gcp -spec: - publicSubnetsCIDRs: - - 10.0.1.0/24 - privateSubnetsCIDRs: - - 10.0.101.0/24 - clusterNetwork: - subnetworkCIDR: 10.1.0.0/16 - podSubnetworkCIDR: 10.2.0.0/16 - serviceSubnetworkCIDR: 10.3.0.0/16 - vpn: - subnetCIDR: 192.168.200.0/24 - sshUsers: - - angelbarrera92 diff --git a/automated-tests/integration/gcp-gke/cluster.yml b/automated-tests/integration/gcp-gke/cluster.yml deleted file mode 100644 index 689bf3818..000000000 --- a/automated-tests/integration/gcp-gke/cluster.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: furyctl -provisioner: gke -spec: - version: 1.20.9-gke.701 - network: furyctl - subnetworks: - - furyctl-cluster-subnet - - furyctl-cluster-pod-subnet - - furyctl-cluster-service-subnet - dmzCIDRRange: 10.0.0.0/8 - sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCefFo9ASM8grncpLpJr+DAeGzTtoIaxnqSqrPeSWlCyManFz5M/DDkbnql8PdrENFU28blZyIxu93d5U0RhXZumXk1utpe0L/9UtImnOGG6/dKv9fV9vcJH45XdD3rCV21ZMG1nuhxlN0DftcuUubt/VcHXflBGaLrs18DrMuHVIbyb5WO4wQ9Od/SoJZyR6CZmIEqag6ADx4aFcdsUwK1Cpc51LhPbkdXGGjipiwP45q0I6/Brjxv/Kia1e+RmIRHiltsVBdKKTL9hqu9esbAod9I5BkBtbB5bmhQUVFZehi+d/opPvsIszE/coW5r/g/EVf9zZswebFPcsNr85+x - nodePools: - - name: my-node-pool - minSize: 1 - maxSize: 1 - volumeSize: 50 - instanceType: n1-standard-1 - - name: my-spot-node-pool - minSize: 1 - maxSize: 1 - volumeSize: 50 - spotInstance: true - instanceType: n1-standard-1 diff --git a/automated-tests/integration/gcp-gke/tests.sh b/automated-tests/integration/gcp-gke/tests.sh deleted file mode 100644 index 183b6e256..000000000 --- a/automated-tests/integration/gcp-gke/tests.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -load "./../../helper" - -OS="linux" -if [[ "${OSTYPE}" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -@test "furyctl" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty version - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Bootstrap init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./automated-tests/integration/gcp-gke/bootstrap.yml -w ./automated-tests/integration/gcp-gke/bootstrap --reset - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Bootstrap structure" { - info - project_dir="./automated-tests/integration/gcp-gke/bootstrap" - test(){ - if [ -e ${project_dir}/bin/terraform ] && [ -e ${project_dir}/configuration/.netrc ] && [ -e ${project_dir}/logs/terraform.logs ] && [ -e ${project_dir}/.gitignore ] && [ -e ${project_dir}/.gitattributes ] && [ -e ${project_dir}/.gitattributes ] - then - echo " All files exist, directory intact" >&3 - return 0 - else - echo " One or more files are missing" >&3 - return 1 - fi - } - run test - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Cluster init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./automated-tests/integration/gcp-gke/cluster.yml -w ./automated-tests/integration/gcp-gke/cluster --reset - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Cluster structure" { - info - project_dir="./automated-tests/integration/gcp-gke/cluster" - test(){ - if [ -e ${project_dir}/bin/terraform ] && [ -e ${project_dir}/configuration/.netrc ] && [ -e ${project_dir}/logs/terraform.logs ] && [ -e ${project_dir}/.gitignore ] && [ -e ${project_dir}/.gitattributes ] && [ -e ${project_dir}/backend.tf ] - then - echo " All files exist, directory intact" >&3 - return 0 - else - echo " One or more files are missing" >&3 - return 1 - fi - } - run test - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/target/kustomization.yaml b/automated-tests/integration/template-engine/test-data/complex-dry-run/target/kustomization.yaml deleted file mode 100644 index 49d485b4d..000000000 --- a/automated-tests/integration/template-engine/test-data/complex-dry-run/target/kustomization.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../../vendor/katalog/ingress/cert-manager - - ../../vendor/katalog/ingress/nginx - - ../../vendor/katalog/ingress/forecastle - - resources/cert-manager-clusterissuer.yml - -patchesStrategicMerge: - - patches/cert-manager.yml - - patches/infra-nodes.yml - - patches/ingress-nginx.yml - -hey: two one diff --git a/automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml b/automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml deleted file mode 100644 index 24e987929..000000000 --- a/automated-tests/integration/template-engine/test-data/complex/source/config/example.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -configdata: example diff --git a/automated-tests/integration/template-engine/test-data/complex/target/config/example.yaml b/automated-tests/integration/template-engine/test-data/complex/target/config/example.yaml deleted file mode 100644 index 24e987929..000000000 --- a/automated-tests/integration/template-engine/test-data/complex/target/config/example.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -configdata: example diff --git a/automated-tests/integration/template-engine/test-data/complex/target/kustomization.yaml b/automated-tests/integration/template-engine/test-data/complex/target/kustomization.yaml deleted file mode 100644 index 3e239a498..000000000 --- a/automated-tests/integration/template-engine/test-data/complex/target/kustomization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../../vendor/katalog/ingress/cert-manager - - ../../vendor/katalog/ingress/nginx - - ../../vendor/katalog/ingress/forecastle - - resources/cert-manager-clusterissuer.yml - -patchesStrategicMerge: - - patches/cert-manager.yml - - patches/infra-nodes.yml - - patches/ingress-nginx.yml diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target/file.txt b/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target/file.txt deleted file mode 100644 index 4abbcbfa2..000000000 --- a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target/file.txt +++ /dev/null @@ -1 +0,0 @@ -testValue diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/target/file.txt b/automated-tests/integration/template-engine/test-data/simple-dry-run/target/file.txt deleted file mode 100644 index 4abbcbfa2..000000000 --- a/automated-tests/integration/template-engine/test-data/simple-dry-run/target/file.txt +++ /dev/null @@ -1 +0,0 @@ -testValue diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/target/keepfile.txt b/automated-tests/integration/template-engine/test-data/simple-dry-run/target/keepfile.txt deleted file mode 100644 index 30d74d258..000000000 --- a/automated-tests/integration/template-engine/test-data/simple-dry-run/target/keepfile.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt b/automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt deleted file mode 100644 index 30d74d258..000000000 --- a/automated-tests/integration/template-engine/test-data/simple/source/keepfile.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/automated-tests/integration/template-engine/test-data/simple/target/file.txt b/automated-tests/integration/template-engine/test-data/simple/target/file.txt deleted file mode 100644 index 4abbcbfa2..000000000 --- a/automated-tests/integration/template-engine/test-data/simple/target/file.txt +++ /dev/null @@ -1 +0,0 @@ -testValue diff --git a/automated-tests/integration/template-engine/test-data/simple/target/keepfile.txt b/automated-tests/integration/template-engine/test-data/simple/target/keepfile.txt deleted file mode 100644 index 30d74d258..000000000 --- a/automated-tests/integration/template-engine/test-data/simple/target/keepfile.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/automated-tests/integration/vsphere/.gitignore b/automated-tests/integration/vsphere/.gitignore deleted file mode 100644 index e89bbc819..000000000 --- a/automated-tests/integration/vsphere/.gitignore +++ /dev/null @@ -1 +0,0 @@ -cluster \ No newline at end of file diff --git a/automated-tests/integration/vsphere/cluster.yml b/automated-tests/integration/vsphere/cluster.yml deleted file mode 100644 index 62c9e1aaa..000000000 --- a/automated-tests/integration/vsphere/cluster.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: furyctl -provisioner: vsphere -executor: - version: 0.12.29 -spec: - version: 1.20.5 - environmentName: demo - config: - datacenterName: SIGHUPLAB - datastore: Datastore2 - esxiHosts: - - sighupesx2.your-server.de - networkConfig: - name: SIGHUP_PROD - nameservers: - - 1.1.1.1 - - 8.8.8.8 - domain: localdomain - lbNode: - count: 1 - template: TEMPLATES-NODE02/sighup-oraclelinux7.9-template-v20210413 - masterNode: - count: 1 - cpu: 1 - memSize: 4096 - diskSize: 100 - template: TEMPLATES-NODE02/sighup-oraclelinux7.9-template-v20210413 - infraNode: - count: 1 - cpu: 1 - memSize: 8192 - diskSize: 100 - template: TEMPLATES-NODE02/sighup-oraclelinux7.9-template-v20210413 - nodePools: [] - clusterPODCIDR: 172.21.0.0/16 - clusterSVCCIDR: 172.23.0.0/16 - clusterCIDR: 10.2.0.0/16 - sshPublicKeys: [] diff --git a/automated-tests/integration/vsphere/tests.sh b/automated-tests/integration/vsphere/tests.sh deleted file mode 100644 index 8b3817b9f..000000000 --- a/automated-tests/integration/vsphere/tests.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -load "./../../helper" - -OS="linux" -if [[ "${OSTYPE}" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64_v1" -# fi - -@test "furyctl" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty version - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Cluster init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init \ - --config ./automated-tests/integration/vsphere/cluster.yml \ - -w ./automated-tests/integration/vsphere/cluster \ - --reset - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Cluster structure" { - info - project_dir="./automated-tests/integration/vsphere/cluster" - test(){ - if [ -e ${project_dir}/provision/ansible.cfg ] && \ - [ -e ${project_dir}/bin/terraform ] && \ - [ -e ${project_dir}/configuration/.netrc ] && \ - [ -e ${project_dir}/logs/terraform.logs ] && \ - [ -e ${project_dir}/.gitignore ] && \ - [ -e ${project_dir}/.gitattributes ] && \ - [ -e ${project_dir}/backend.tf ] - then - echo " All files exist, directory intact" >&3 - return 0 - else - echo " One or more files are missing" >&3 - return 1 - fi - } - run test - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} diff --git a/cmd/root.go b/cmd/root.go index 04dd0a152..633d2beed 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,9 +12,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/io" - "github.com/sighupio/furyctl/pkg/analytics" ) type rootConfig struct { diff --git a/data/provisioners/bootstrap/gcp/main.tf b/data/provisioners/bootstrap/gcp/main.tf deleted file mode 100644 index 7e4ae41cb..000000000 --- a/data/provisioners/bootstrap/gcp/main.tf +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -terraform { - required_version = "~> 0.15.4" - required_providers { - external = "~> 2.0.0" - google = "~> 3.55.0" - local = "~> 2.0.0" - null = "~> 3.0.0" - random = "~> 3.0.1" - } -} - -provider "google" { - project = var.provider_project - region = var.provider_region -} - -module "vpc-and-vpn" { - source = "github.com/sighupio/fury-gke-installer//modules/vpc-and-vpn?ref=v1.10.0" - - name = var.name - public_subnetwork_cidrs = var.public_subnetwork_cidrs - private_subnetwork_cidrs = var.private_subnetwork_cidrs - cluster_control_plane_cidr_block = var.cluster_control_plane_cidr_block - cluster_subnetwork_cidr = var.cluster_subnetwork_cidr - cluster_pod_subnetwork_cidr = var.cluster_pod_subnetwork_cidr - cluster_service_subnetwork_cidr = var.cluster_service_subnetwork_cidr - vpn_subnetwork_cidr = var.vpn_subnetwork_cidr - tags = var.tags - vpn_instances = var.vpn_instances - vpn_port = var.vpn_port - vpn_instance_type = var.vpn_instance_type - vpn_instance_disk_size = var.vpn_instance_disk_size - vpn_operator_name = var.vpn_operator_name - vpn_dhparams_bits = var.vpn_dhparams_bits - vpn_operator_cidrs = var.vpn_operator_cidrs - vpn_ssh_users = var.vpn_ssh_users -} diff --git a/data/provisioners/bootstrap/gcp/output.tf b/data/provisioners/bootstrap/gcp/output.tf deleted file mode 100644 index 0d8567706..000000000 --- a/data/provisioners/bootstrap/gcp/output.tf +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -output "furyagent" { - description = "furyagent.yml used by the vpn instance and ready to use to create a vpn profile" - sensitive = true - value = module.vpc-and-vpn.furyagent -} - -output "vpn_ip" { - description = "VPN instance IP" - value = module.vpc-and-vpn.vpn_ip -} - -output "vpn_operator_name" { - description = "SSH Username to log into the VPN Instance" - value = var.vpn_operator_name -} - -output "network_name" { - description = "The name of the network" - value = module.vpc-and-vpn.network_name -} - -output "public_subnets" { - description = "List of names of public subnets" - value = module.vpc-and-vpn.public_subnets -} - -output "public_subnets_cidr_blocks" { - description = "List of cidr_blocks of public subnets" - value = module.vpc-and-vpn.public_subnets_cidr_blocks -} - -output "private_subnets" { - description = "List of names of private subnets" - value = module.vpc-and-vpn.private_subnets -} - -output "private_subnets_cidr_blocks" { - description = "List of cidr_blocks of private subnets" - value = module.vpc-and-vpn.private_subnets_cidr_blocks -} - -output "cluster_subnet" { - description = "Name of the cluster subnets" - value = module.vpc-and-vpn.cluster_subnet -} - -output "cluster_subnet_cidr_blocks" { - description = "List of cidr_blocks of private subnets" - value = module.vpc-and-vpn.cluster_subnet_cidr_blocks -} - -output "additional_cluster_subnet" { - description = "List of cidr_blocks of private subnets" - value = length(module.vpc-and-vpn.additional_cluster_subnet) == 1 ? module.vpc-and-vpn.additional_cluster_subnet[0] : [] -} diff --git a/data/provisioners/bootstrap/gcp/variables.tf b/data/provisioners/bootstrap/gcp/variables.tf deleted file mode 100644 index 87696c0c2..000000000 --- a/data/provisioners/bootstrap/gcp/variables.tf +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -variable "provider_project" { - description = "Name of the Provider's project" - type = string -} - -variable "provider_region" { - description = "Name of the Provider's region" - type = string -} - -variable "name" { - description = "Name of the resources. Used as cluster name" - type = string -} - -variable "public_subnetwork_cidrs" { - description = "Public subnet CIDRs" - type = list(string) -} - -variable "private_subnetwork_cidrs" { - description = "Private subnet CIDRs" - type = list(string) -} - -variable "cluster_control_plane_cidr_block" { - description = "Private subnet CIDR hosting the GKE control plane" - type = string - default = "10.0.0.0/28" -} - -variable "cluster_subnetwork_cidr" { - description = "Private subnet CIDR" - type = string -} - -variable "cluster_pod_subnetwork_cidr" { - description = "Private subnet CIDR" - type = string -} - -variable "cluster_service_subnetwork_cidr" { - description = "Private subnet CIDR" - type = string -} - -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} -} - -variable "vpn_subnetwork_cidr" { - description = "VPN Subnet CIDR, should be different from the network_cidr" - type = string -} - -variable "vpn_instances" { - description = "VPN Servers" - type = number - default = 1 -} - -variable "vpn_port" { - description = "VPN Server Port" - type = number - default = 1194 -} - -variable "vpn_instance_type" { - description = "GCP instance type" - type = string - default = "n1-standard-1" -} - -variable "vpn_instance_disk_size" { - description = "VPN main disk size" - type = number - default = 50 -} - -variable "vpn_operator_name" { - description = "VPN operator name. Used to log into the instance via SSH" - type = string - default = "sighup" -} - -variable "vpn_dhparams_bits" { - description = "Diffie–Hellman (D-H) key size in bytes" - type = number - default = 2048 -} - -variable "vpn_operator_cidrs" { - description = "VPN Operator cidrs. Used to log into the instance via SSH" - type = list(string) - default = ["0.0.0.0/0"] -} - -variable "vpn_ssh_users" { - description = "GitHub users id to sync public rsa keys. Example angelbarrera92" - type = list(string) -} diff --git a/data/provisioners/cluster/eks/variables.tf b/data/provisioners/cluster/eks/variables.tf index 4f8a2bb0c..eccbd14dd 100644 --- a/data/provisioners/cluster/eks/variables.tf +++ b/data/provisioners/cluster/eks/variables.tf @@ -11,7 +11,7 @@ variable "cluster_name" { variable "cluster_version" { type = string - description = "Kubernetes Cluster Version. Look at the cloud providers documentation to discover available versions. EKS example -> 1.16, GKE example -> 1.16.8-gke.9" + description = "Kubernetes Cluster Version. Look at the cloud providers documentation to discover available versions. EKS example -> 1.23" } variable "cluster_log_retention_days" { diff --git a/data/provisioners/cluster/gke/main.tf b/data/provisioners/cluster/gke/main.tf deleted file mode 100644 index e5818c928..000000000 --- a/data/provisioners/cluster/gke/main.tf +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -terraform { - experiments = [module_variable_optional_attrs] - required_version = "~> 0.15.4" - required_providers { - external = "~> 2.0.0" - google = "~> 3.55.0" - google-beta = "~> 3.55.0" - kubernetes = "~> 1.13.3" - null = "~> 3.0.0" - random = "~> 3.0.1" - } -} - -provider "google" { - project = var.provider_project - region = var.provider_region -} - -module "fury" { - source = "github.com/sighupio/fury-gke-installer//modules/gke?ref=v1.10.0" - - cluster_name = var.cluster_name - cluster_version = var.cluster_version - network = var.network - subnetworks = var.subnetworks - dmz_cidr_range = var.dmz_cidr_range - ssh_public_key = var.ssh_public_key - node_pools = var.node_pools - tags = var.tags - - # Specific GKE variables. - gke_master_ipv4_cidr_block = var.gke_master_ipv4_cidr_block - gke_add_additional_firewall_rules = var.gke_add_additional_firewall_rules - gke_add_cluster_firewall_rules = var.gke_add_cluster_firewall_rules - gke_disable_default_snat = var.gke_disable_default_snat -} diff --git a/data/provisioners/cluster/gke/output.tf b/data/provisioners/cluster/gke/output.tf deleted file mode 100644 index 187f2fe57..000000000 --- a/data/provisioners/cluster/gke/output.tf +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -output "cluster_endpoint" { - sensitive = true - description = "The endpoint for your Kubernetes API server" - value = module.fury.cluster_endpoint -} - -output "cluster_certificate_authority" { - sensitive = true - description = "The base64 encoded certificate data required to communicate with your cluster. Add this to the certificate-authority-data section of the kubeconfig file for your cluster" - value = module.fury.cluster_certificate_authority -} - -output "operator_ssh_user" { - description = "SSH user to access cluster nodes with ssh_public_key" - value = module.fury.operator_ssh_user -} - -data "google_client_config" "current" {} - -output "kubeconfig" { - sensitive = true - value = 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 diff --git a/docs/design/001-Cluster-Self-Provisioning.md b/docs/design/001-Cluster-Self-Provisioning.md deleted file mode 100644 index 793c51e9c..000000000 --- a/docs/design/001-Cluster-Self-Provisioning.md +++ /dev/null @@ -1,146 +0,0 @@ -# LGTM (Looks Good To Me) - -- [x] Gabriele Lana -- [x] Jacopo Nardiello -- [ ] Gabriele Lana -- [x] Luca Novara -- [ ] Philippe Scorsolini - -> If you think someone else can review this document, feel free to add them into the list. - -# Description+ - -The main goal of this document is to define the **MVP** of the self-service cluster creation feature for the `furyctl` -binary. Currently `furyctl` is underused, it is used to vendor the artifacts specified in a `Furyctl.yml` and -also to download the right `Furyctl.yml` plus `kustomization.yml` files while running the `furyctl init` -command *(for the distribution)*. - -It is time to extend its functionalities to provide a way to self-provision Kubernetes clusters. - -# Problem+ - -The main reason to start this feature is to make it simpler to deploy a production-grade cluster from a SIGHUP -well-known command-line interface (CLI) tool: `furyctl`. - -It is not only about deploying (one shoot) Kubernetes Clusters, it has to be maintained during the time, so any -kind of action has to be done via `furyctl`. - -Even being open source, SIGHUP Fury stack is not easy to use by someone on the internet *(aka community)*. -This situation limits the target of possible clients as no one can try Fury Clusters before considering hiring us. - -# Solution+ - -## Context+ - -We already did a POC in a separate binary. The idea is to reuse as much as possible our SIGHUP current stack -(terraform, go...) to create a subcommand in the `furyctl` CLI. - -## Implementation+ - -We have to implement the `cluster` subcommand under the `furyctl` binary to be able to manage the complete lifecycle -of an EKS cluster using the current [SIGHUP EKS Cloud installer](https://github.com/sighupio/fury-eks-installer). - -The implementation will: - -- (Optional) Create a VPC with a VPN Host. The command will be something like: `furyctl bootstrap {init,update,destroy} --config bootstrap.yml` - - The VPN server has to provide auto recovery in case an outage happens. TL;DR use `furyagent` to init - - Then, to create a production-grade cluster, the operator has to connect to the VPN server. -- Create a cluster in a user-defined network. As the first implementation is an EKS private cluster, a pre-requirement will be to stay connected in the VPN created by the bootstrap stage. - - The provisioner has to warn the operator if it needs to be connected to a VPN or deploy the cluster using a bastion host. (It has to have connectivity to the end cluster) - - -### Bootstrap - -The bootstrap has to initialize a network and deploy a VPN host in the public subnet as we are going to deploy only private clusters -> Nice link: https://learn.hashicorp.com/tutorials/terraform/eks - -The VPN host has to use our `furyagent` functionalities to: -- Init the configuration (both VPN and ssh users). -- Configure the VPN software. -- Configure the ssh system user. - - -A warning has to be raised if the terraform state configuration is a file. - -### Cluster - -The cluster has to be provided through the bastion host or using a previously configured VPN. One of both alternatives -it's required because some clusters providers require to have connectivity to the API server (private, not public-facing) - -A warning has to be raised if the terraform state configuration is a file. - -## Constraints+ - -- The owner of the task has limited experience developing golang code. -- It has to be integrated within `furyctl` and use `furyagent`. - -## Risks+ - -The possibility of failing during the implementation is there: - -- We could not manage the complete lifecycle -- We could end up with a tool that is not used by anyone in the company/community - - -## Considered Alternatives+ - -We found multiple alternatives; some of them got a try: - -- [cluster-api](https://github.com/kubernetes-sigs/cluster-api): We thought about using it. We found some stoppers here: - - It is super tricky, and the learning curve is too much - - In the cluster-api docs we can read an ample warning: - - Cluster API is still in the prototype stage while we get feedback on the API types themselves. All the code here is to experiment with the API and demo its abilities, in order to drive more technical feedback to the API design. Because of this, all the codebases is rapidly changing. - - Source: https://github.com/kubernetes-sigs/cluster-api#what-is-the-cluster-api - - If we see other competitors, like RedHat OCP4 installer, we can see they are using both options, cluster-api and terraform bundled in the CLI. -- [Terranova](https://github.com/johandry/terranova): looks like an excellent project, but there were a couple of problems with it: - - Providers versions have to be statically defined; in addition to it, not all providers’ versions are currently compatible. - - It seems to be maintained by a single person. -- [Terraform](https://github.com/hashicorp/terraform): Using the terraform code natively could be a good idea. For sure more complicated than using Terranova (Terranova was born because using terraform golang code is complex) -Angel’s skills are not good enough to develop this CLI starting from the terraform code. -- [Terraform-exec](https://github.com/hashicorp/terraform-exec): it is a project maintained by hashicorp, making easy execute terraform code from golang. It is just a wrapper around a terraform binary. -As you can see, this seems to be the easier path with our current environment. - - -## Testing+ - -It has to automate test: - -- unit-testing -- integration tests -- e2e tests - -Then manual tests have to be done on real providers. - -## Deploy / Rollout+ - -The deployment of new releases has to be triggered automatically via drone + GitHub releases. -It has to be available via brew or download it via the GitHub release download link. - -## Metrics - -As we experimented with mixpanel, we can continue adding metrics there. We can monitor (if possible): - -- Number of Downloads of the `furyctl` binary - - Platforms - - Version -- Number of provisioner clusters - - Provisioner -- Number of updated clusters -- Number of destroyed clusters. - -## Documentation - -- kubernetesfury.com has to be updated - - cli section - - create a new section under installers -- community docs in the repository. - - tl;dr with a working example - -## Future Opportunities - -This feature will enable multiple streams: - -- The community will be able to test Fury. This could potentially end up with some ping from enterprise to get support -on it. -- Current clients will be able to spin up new Clusters without the delivery team. Then we can bill them by the -CPU/node if they require support on these clusters. diff --git a/docs/design/002-Cluster-Self-Provisioning - GKE.md b/docs/design/002-Cluster-Self-Provisioning - GKE.md deleted file mode 100644 index a4c53c88e..000000000 --- a/docs/design/002-Cluster-Self-Provisioning - GKE.md +++ /dev/null @@ -1,132 +0,0 @@ -# LGTM (Looks Good To Me) - -- [ ] Gabriele Lana -- [ ] Jacopo Nardiello -- [ ] Luca Novara -- [ ] Niccolo' Raspa - -> If you think someone else can review this document, feel free to add them to the list. - -# Description+ - -This document's primary goal is to define the second phase of the self-service cluster creation feature for the -`furyctl` binary. Currently, `furyctl` is underused; it can only deploy AWS/EKS clusters along with the -requirements to deploy it (bastion/VPN, VPC, and so on). - -It is time to extend its functionalities to provide a way to self-provision Kubernetes clusters on Google Cloud / GKE. - -# Problem+ - -As mentioned above, the main problem to solve with this CVI is to enable other cloud providers into this binary. -The main goal is to continue enabling new cloud providers to deploy Kubernetes clusters. - -The next cloud provider to enable in this binary should be Google Cloud and GKE. - -It is not only about deploying (one shoot) Kubernetes Clusters; it has to be maintained during the time, so any -kind of action has to be done via `furyctl`. - -Even being open source, SIGHUP Fury stack is not easy to use by someone on the internet *(aka community)*. -This situation limits possible clients' target as no one can try Fury Clusters before considering hiring us. - -# Solution+ - -## Context+ - -[The initial design of the cluster self-provisioning feature](001-Cluster-Self-Provisioning.md) -enables the easy extension of it with new provisioners. - -## Implementation+ - -We have to extend the `cluster` and `bootstrap` subcommand under the `furyctl` binary to be able to manage the complete -lifecycle of a GKE cluster using the current [SIGHUP GKE Cloud installer](https://github.com/sighupio/fury-gke-installer). - -The implementation will: - -- (Optional) Create a Network with a VPN Host. The command will be something like: `furyctl bootstrap {init,apply,destroy} --config bootstrap.yml` - - The VPN server has to provide auto recovery in case an outage happens. TL;DR use `furyagent` to init. - - Then, to create a production-grade cluster, the operator has to connect to the VPN server. -- Create a cluster in a user-defined network. As the first implementation is a GKE private cluster, a pre-requirement will stay connected in the VPN created by the bootstrap stage. - - The provisioner has to warn the operator if it needs to be connected to a VPN or deploy the cluster using a bastion host. (It has to have connectivity to the end cluster) - - -### Bootstrap - -The bootstrap has to initialize a network and deploy a VPN host in the public subnet as we are going to deploy only private clusters - -The VPN host has to use our `furyagent` functionalities to: -- Init the configuration (both VPN and ssh users). -- Configure the VPN software. -- Configure the ssh system user. - -Raise a warning if the terraform state configuration is a file. - -### Cluster - -The cluster has to be provided through the bastion host or using a previously configured VPN. One of both alternatives -it's required because some clusters providers need to have connectivity to the API server (private, not public-facing) - -Raise a warning if the terraform state configuration is a file. - -## Constraints+ - -- Have to be integrated within `furyctl` and use `furyagent`. - -## Risks+ - -The possibility of failing during the implementation is there: - -- We could not manage the complete lifecycle. -- We could end up with a tool not used by anyone in the company/community. - - -## Considered Alternatives+ - -> [The initial design already addressed other alternatives](001-Cluster-Self-Provisioning.md) - -We selected google cloud as the next provisioner in `furyctl` instead of azure because the AKS installer has some manual -interactions. It makes it challenging to automate the completed workflow. - - -## Testing+ - -It has to automate test: - -- unit-testing -- integration-tests -- e2e-tests - -Then manual tests have to be done on real providers. - -## Deploy / Rollout+ - -The deployment of new releases has to be triggered automatically via drone + GitHub releases. -It has to be available via brew or download via the GitHub release download link. - -## Metrics - -As we experimented with mixpanel, we can continue adding metrics there. We can monitor (if possible): - -- Number of Downloads of the `furyctl` binary - - Platforms - - Version -- Number of provisioner clusters - - Provisioner -- Number of updated clusters -- Number of destroyed clusters. - -## Documentation - -- kubernetesfury.com has to be updated - - CLI section - - create a new section under installers -- community docs in the repository. - - tl;dr with a working example - -## Future Opportunities - -This feature will enable multiple streams: - -- The community will be able to test Fury. This could potentially end up with some ping from enterprise to get support -on it. -- Current clients will be able to spin up new Clusters without the delivery team. Then we can bill them by the -CPU/node if they require support on these clusters. diff --git a/docs/design/003-Fury-vShpere-Provisioner.md b/docs/design/003-Fury-vShpere-Provisioner.md deleted file mode 100644 index cde072534..000000000 --- a/docs/design/003-Fury-vShpere-Provisioner.md +++ /dev/null @@ -1,143 +0,0 @@ -# LGTM (Looks Good To Me) - -- [ ] Giovanni Laieta -- [ ] Ramiro Algozino -- [ ] Luca Novara -- [ ] Luca Zecca - -# Status+ - -_Done_ - -# Description+ - -Integrate vShpere as provisioner of clusters in `furyctl`. -Due to budget (time) contraints we will only support the creation of -clusters (`furyctl cluster`) assuming an already working -infrastructure (`furyctl bootstrap`) that in this case must be done -manually. - -# Problem+ - -Being able to create Fury clusters using vSphere node provider. - -# Solution+ - -## Context+ - -We currently use a set of roles to deploy infrastructure on top of any on-premise -servers. We can use these resources to deploy the cluster on top of VM created with -terraform. These ansible roles do not allow us to manage the day two operations as we -are currently managing it with terraform on the cloud-installers. So we are going to -start supporting the creation of clusters on vpshere along some basic day two -operations like adding or scaling node pools. - -## Implementation+ - -### Plan - -- Create a workable Oracle Linux image. -- Take the work done (Terraform, Ansible, ...) on vShpere lab with - Ubuntu instances and make it work for Oracle Linux instances. -- Make the above work general and configurable (ex. number of worker - nodes, labeling, etc...). -- Make it run with `furyctl cluster` command. -- Add end2end tests. -- Write documentation. - -## Constraints+ - -- It's an enterprise feature so the provisioner should be kept private - to SIGHUP organization. -- Must support two kinds of VM images: Ubuntu 20 and Oracle Linux 7.9. -- Must support customization hook for VMs that can be used during - installation. - - We decided to enable the user to provide an input variable per node - to specify a local script path. It will be executed doring first boot. -- As mentioned above, the implementation will allow to safely create -clusters while the day two operations must be performed as usual. - -## Risks+ - -- Requirements for VM images could be too strict (the customization - hook can be used to reduce the risk starting from a general golden - image). - - We can provide the customer the recipe to bake a VM template for - Ubuntu 20 and Oracle Linux 7.9 -- Compatibility between Kubernetes (of version X) and vSphere (of - version Y). -- vSphere version is a risk. We only have one invironment were to test -the development. - - -## Considered Alternatives+ - -Not applicable, the only alternative is to do everything by hand. - -## Deliverables+ - -- [Provisioner repository](https://github.com/sighupio/furyctl-provisioners) -- Furyctl that will be able to use the new provisioner. -- An Ubuntu golden image recipe. -- An Oracle Linux 7.0 golden image recipe. -- Documention (see below) - -## Infrastructure Requirements+ - -- vSphere version 6.5 ([terraform requirement](https://github.com/hashicorp/terraform-provider-vsphere)) - - vSphere version 6.7 required to install the CSI: https://github.com/kubernetes-sigs/vsphere-csi-driver - - vSphere version 6.7 required to install the CPI: https://cloud-provider-vsphere.sigs.k8s.io/tutorials/kubernetes-on-vsphere-with-kubeadm.html -- The vSphere integration has to be performed after cluster creation. -- We will not consider the storage integration as part of the problem - because vSphere it will not be compatible with Kubernetes... - -## Testing+ - -- An end2end test for this provisioner will be integrated in the suite - of end2end tests for other provisioners. - -## Deploy/Rollout+ - -- The deployment is going to be managed as usual with an automated pipeline. - -## Metrics - -- Add a new id to the current metrics to identify vSphere provisions. -- Manage the same way we are managing the other provisioners. - -## Documentation+ - -- Documentation for the customers (on constraints, how to use it) -- Documentation for the technical end user (add to Fury documentation, - don't forget the compatibility matrix with vShpere versions) -- Usage demo -- Sales proposition/values? - -## Security - -... - -## Privacy - -... - -## Scaling - -... - -# After+ - -We have to consider adding day two operations like: -- Certificate Renewal -- Cluster Upgrade - -## What We Learned+ - -... - -## Left For Later+ - -- Handle lifecycle of vShper nodes (TODO: ask Angel) -- Expose the ability to run customization script in `furyctl` - configuration file so that it will be available for all - provisioners. diff --git a/docs/upgrading_to_v0.5.1.md b/docs/upgrading_to_v0.5.1.md deleted file mode 100644 index 795f3ac70..000000000 --- a/docs/upgrading_to_v0.5.1.md +++ /dev/null @@ -1,215 +0,0 @@ -# Upgrading to v0.5.1 - -If you are a `furyctl` user with a prior version from v0.5.1, you could be affected by the breaking changes introduced -in this new release `(v0.5.1)`. - -The following `executor` attributes *(`path` and `version`)* has been deprecated to make your life easier -in future upgrades. - -```yaml -... -executor: # You couldn't use both attributes at the same time. Both attributes have been deprecated - path: /usr/local/bin/terraform - version: 0.12.28 -... -``` - -So, if you already have a project using an executor version different to `0.15.4` -you need to follow one of the paths bellows: - -- [Upgrading to v0.5.1](#upgrading-to-v051) - - [I am in 0.12.X](#i-am-in-012x) - - [I am in 0.13.X, 0.14.X or 0.15.X](#i-am-in-013x-014x-or-015x) -- [IMPORTANT Notes](#important-notes) - -## I am in 0.12.X - -> The following provisioners could be affected. It depends on your `executor.version` or `executor.path` definition. -> aws bootstrap -> eks cluster - -If you already have deployed a `{cluster, bootrap}` using furyctl version `< 0.5.1` and you used an executor version -`0.12.X` like this one: - -```yaml -kind: Bootstrap -metadata: - name: demo -provisioner: aws -executor: - version: 0.12.29 - state: - backend: s3 - config: - bucket: fury-testing - key: furyctl-upgrade-test - region: eu-west-1 -spec: - networkCIDR: 10.0.0.0/16 - publicSubnetsCIDRs: - - 10.0.20.0/24 - - 10.0.30.0/24 - privateSubnetsCIDRs: - - 10.0.182.0/24 - - 10.0.192.0/24 - vpn: - instances: 1 - instanceType: t3.micro - operatorName: sighup - subnetCIDR: 172.16.0.0/16 - sshUsers: - - angelbarrera92 - operatorCIDRs: - - 54.27.48.48/32 -``` - -You have to manually migrate the terraform project to `0.13.X` by downloading a terraform `0.13.X` version. Then: - -```bash -$ cd bootstrap/ -$ terraenv terraform install 0.13.7 -Downloading terraform 0.13.7 from https://releases.hashicorp.com/terraform/0.13.7/terraform_0.13.7_darwin_amd64.zip -terraform version is set to 0.13.7 -$ terraform version -Terraform v0.13.7 - -Your version of Terraform is out of date! The latest version -is 0.15.4. You can update by downloading from https://www.terraform.io/downloads.html -$ terraform 0.13upgrade -yes - -Upgrade complete! - -Use your version control system to review the proposed changes, make any -necessary adjustments, and then commit. -$ cd .. -``` - -At this point, you have to modify the `bootstrap.yml` file to change the `executor.version`: - -```yaml -kind: Bootstrap -metadata: - name: demo -provisioner: aws -executor: - version: 0.13.7 # Place here the latest 0.13 available version - state: - backend: s3 - config: - bucket: fury-testing - key: furyctl-upgrade-test - region: eu-west-1 -spec: - ... -``` - -Then run: - -```bash -$ furyctl bootstrap apply --config bootstrap.yml --reconfigure -``` - -**`WARNING`** Don't forget the `--reconfigure` flag. - -After the command finishes, **download the new `furyctl` version (`v0.5.1`).** -Then modify again the `bootstrap.yml` file in order to remove the `executor.version`: - -```yaml -kind: Bootstrap -metadata: - name: demo -provisioner: aws -executor: - state: - backend: s3 - config: - bucket: fury-testing - key: furyctl-upgrade-test - region: eu-west-1 -spec: - ... -``` - -Finally, run: - -```bash -$ furyctl bootstrap init --reset --config bootstrap.yml --reconfigure -# IN CASE YOU ARE USING THE CLUSTER vSphere PROVISIONER, READ THE NOTE AT THE END OF THIS DOCUMENT (*) -$ furyctl bootstrap apply --config bootstrap.yml -``` - -## I am in 0.13.X, 0.14.X or 0.15.X - -> The following provisioners could be affected. It depends on your `executor.version` or `executor.path` definition. -> aws bootstrap -> eks cluster -> gcp bootstrap -> gke cluster -> vsphere cluster - -If you already have deployed a `{cluster, bootrap}` using furyctl version `< 0.5.1` and you used an executor version -`0.1{3,4,5}.X` like this one: - -```yaml -kind: Bootstrap -metadata: - name: demo -provisioner: gcp -executor: - version: 0.13.7 - state: - backend: s3 - config: - bucket: fury-testing - key: furyctl-upgrade-test - region: eu-west-1 -spec: - publicSubnetsCIDRs: - - 10.0.1.0/24 - privateSubnetsCIDRs: - - 10.0.101.0/24 - clusterNetwork: - subnetworkCIDR: 10.1.0.0/16 - podSubnetworkCIDR: 10.2.0.0/16 - serviceSubnetworkCIDR: 10.3.0.0/16 - vpn: - instances: 1 - subnetCIDR: 192.168.200.0/24 - sshUsers: - - angelbarrera92 -``` - -**Download the new `furyctl` version (`0.5.1`).** -Then modify again the `bootstrap.yml` file in order to remove the `executor.version`: - -```yaml -kind: Bootstrap -metadata: - name: demo -provisioner: gcp -executor: - state: - backend: s3 - config: - bucket: fury-testing - key: furyctl-upgrade-test - region: eu-west-1 -spec: - ... -``` - -Finally, run: - -```bash -$ furyctl bootstrap init --reset --config bootstrap.yml --reconfigure -# IN CASE YOU ARE USING THE CLUSTER vSphere PROVISIONER, READ THE NOTE AT THE END OF THIS DOCUMENT (*) -$ furyctl bootstrap apply --config bootstrap.yml -``` - -**`WARNING`** Don't forget the `--reconfigure` flag. - -# IMPORTANT Notes - -- **(*)**: By running `furyctl cluster init --reset --config cluster.yml --reconfigure` with the vSphere provisioner, -it recreates the PKI of the cluster. Make sure you backup it *(or you have everything versioned in git)* before -run this command. After run `init` and before run `apply`, restore the PKI to don't break the cluster. diff --git a/go.mod b/go.mod index a2b9c6762..636e32a7a 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,6 @@ require ( github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/go.sum b/go.sum index e1277df4b..3cf1033d1 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -341,8 +339,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/relex/aini v1.2.1 h1:EN89emh21lgFi5BlLlMEB6IfZKbQ4kUD4vOXyla/Ewk= -github.com/relex/aini v1.2.1/go.mod h1:oFQyhvkzwi8GChiLukpBHkV2v142ls2L1MTeOSD2vic= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= diff --git a/pkg/analytics/analytics.go b/internal/analytics/analytics.go similarity index 100% rename from pkg/analytics/analytics.go rename to internal/analytics/analytics.go diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 56afeb853..6f6abf32b 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -17,7 +17,7 @@ import ( "github.com/sighupio/furyctl/internal/configuration" "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/internal/provisioners" - "github.com/sighupio/furyctl/pkg/terraform" + "github.com/sighupio/furyctl/internal/terraform" ) const initExecutorMessage = " Initializing the terraform executor" diff --git a/internal/bootstrap/configuration/gcp.go b/internal/bootstrap/configuration/gcp.go deleted file mode 100644 index bd5724c7a..000000000 --- a/internal/bootstrap/configuration/gcp.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -// GCP represents the configuration spec of a AWS bootstrap project including VPC and VPN -type GCP struct { - PublicSubnetsCIDRs []string `yaml:"publicSubnetsCIDRs"` - PrivateSubnetsCIDRs []string `yaml:"privateSubnetsCIDRs"` - ClusterNetwork GCPClusterNetwork `yaml:"clusterNetwork"` - VPN GCPVPN `yaml:"vpn"` - Tags map[string]string `yaml:"tags"` - Region string `yaml:"region"` - Project string `yaml:"project"` -} - -// GCPClusterNetwork represents the cluster network configuration -type GCPClusterNetwork struct { - SubnetworkCIDR string `yaml:"subnetworkCIDR"` - ControlPlaneCIDR string `yaml:"controlPlaneCIDR"` - PodSubnetworkCIDR string `yaml:"podSubnetworkCIDR"` - ServiceSubnetworkCIDR string `yaml:"serviceSubnetworkCIDR"` -} - -// GCPVPN represents an VPN configuration -type GCPVPN struct { - Instances int `yaml:"instances"` - Port int `yaml:"port"` - InstanceType string `yaml:"instanceType"` - DiskSize int `yaml:"diskSize"` - OperatorName string `yaml:"operatorName"` - DHParamsBits int `yaml:"dhParamsBits"` - SubnetCIDR string `yaml:"subnetCIDR"` - SSHUsers []string `yaml:"sshUsers"` - OperatorCIDRs []string `yaml:"operatorCIDRs"` -} diff --git a/internal/bootstrap/provisioners/gcp/provisioner.go b/internal/bootstrap/provisioners/gcp/provisioner.go deleted file mode 100644 index 845e6b48c..000000000 --- a/internal/bootstrap/provisioners/gcp/provisioner.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gcp - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "strings" - - "github.com/gobuffalo/packr/v2" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - cfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - "github.com/sighupio/furyctl/internal/configuration" -) - -// InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated -func (d *GCP) InitMessage() string { - return `[GCP] - VPC and VPN - -This provisioner creates a battle-tested GCP VPC with all the requirements -set to run a production-grade private GKE cluster. - -It creates VPN servers enables deploying the cluster from this computer -once connected to the VPN server. - -Then, use furyagent to manage VPN profiles. -` -} - -// UpdateMessage return a custom provisioner message the user will see once the cluster is updated -func (d *GCP) UpdateMessage() string { - var output map[string]tfexec.OutputMeta - output, err := d.terraform.Output(context.Background()) - if err != nil { - logrus.Error("Can not get output values") - } - spec := d.config.Spec.(cfg.GCP) - sshUsers := spec.VPN.SSHUsers - var vpnOperatorName, networkName, clusterSubnet, podSubnet, serviceSubnet string - var vpnInstanceIPs, publicSubnetsIDs, privateSubnetsIDs []string - - type subnets map[string]string - - var additionalClusterSubnet []subnets - - err = json.Unmarshal(output["vpn_ip"].Value, &vpnInstanceIPs) - if err != nil { - logrus.Error("Can not get `vpn_ip` value") - } - err = json.Unmarshal(output["vpn_operator_name"].Value, &vpnOperatorName) - if err != nil { - logrus.Error("Can not get `vpn_operator_name` value") - } - err = json.Unmarshal(output["network_name"].Value, &networkName) - if err != nil { - logrus.Error("Can not get `network_name` value") - } - err = json.Unmarshal(output["public_subnets"].Value, &publicSubnetsIDs) - if err != nil { - logrus.Error("Can not get `public_subnets` value") - } - err = json.Unmarshal(output["private_subnets"].Value, &privateSubnetsIDs) - if err != nil { - logrus.Error("Can not get `private_subnets` value") - } - err = json.Unmarshal(output["cluster_subnet"].Value, &clusterSubnet) - if err != nil { - logrus.Error("Can not get `cluster_subnet` value") - } - err = json.Unmarshal(output["additional_cluster_subnet"].Value, &additionalClusterSubnet) - if err != nil { - logrus.Error("Can not get `additional_cluster_subnet` value") - } - - for _, subnet := range additionalClusterSubnet { - if strings.Contains(subnet["name"], "pod-subnet") { - podSubnet = subnet["name"] - } else if strings.Contains(subnet["name"], "service-subnet") { - serviceSubnet = subnet["name"] - } - } - - vpnFragment := "" - if len(vpnInstanceIPs) > 0 { - vpnSSHFragment := "" - for _, server := range vpnInstanceIPs { - vpnSSHFragment = vpnSSHFragment + fmt.Sprintf("$ ssh %v@%v\n", vpnOperatorName, server) - } - vpnFragment = fmt.Sprintf(` -Your VPN instance IPs are: %v -Use the ssh %v username to access the VPN instance with any SSH key configured -for the following GitHub users: %v. - -%v`, vpnInstanceIPs, vpnOperatorName, sshUsers, vpnSSHFragment) - } - - return fmt.Sprintf(`[GCP] - VPC and VPN - -All the bootstrap components are up to date. - -VPC and VPN ready: - -VPC: %v -Public Subnets : %v -Private Subnets : %v -Cluster Subnet : %v - Pod Subnet : %v - Service Subnet: %v -%v -Then create a openvpn configuration (ovpn) file using the furyagent cli: - -$ furyagent configure openvpn-client --client-name --config %v/secrets/furyagent.yml > .ovpn - -Discover already registered vpn clients running: - -$ furyagent configure openvpn-client --list --config %v/secrets/furyagent.yml - -IMPORTANT! Connect to the VPN with the created ovpn profile to continue deploying -an GKE Kubernetes cluster. -`, networkName, publicSubnetsIDs, privateSubnetsIDs, clusterSubnet, podSubnet, serviceSubnet, vpnFragment, d.terraform.WorkingDir(), d.terraform.WorkingDir()) -} - -// DestroyMessage return a custom provisioner message the user will see once the cluster is destroyed -func (d *GCP) DestroyMessage() string { - return `[GCP] - VPC and VPN -All bootstrap components were destroyed. -VPN and VPC went away. - -Had problems, contact us at sales@sighup.io. -` -} - -// Enterprise return a boolean indicating it is an enterprise provisioner -func (d *GCP) Enterprise() bool { - return false -} - -// GCP represents a dummy provisioner -type GCP struct { - terraform *tfexec.Terraform - box *packr.Box - config *configuration.Configuration -} - -const ( - projectPath = "../../../../data/provisioners/bootstrap/gcp" -) - -func (d GCP) createVarFile() (err error) { - var buffer bytes.Buffer - spec := d.config.Spec.(cfg.GCP) - - buffer.WriteString(fmt.Sprintf("provider_region = \"%v\"\n", spec.Region)) - buffer.WriteString(fmt.Sprintf("provider_project = \"%v\"\n", spec.Project)) - - buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", d.config.Metadata.Name)) - buffer.WriteString(fmt.Sprintf("public_subnetwork_cidrs = [\"%v\"]\n", strings.Join(spec.PublicSubnetsCIDRs, "\",\""))) - buffer.WriteString(fmt.Sprintf("private_subnetwork_cidrs = [\"%v\"]\n", strings.Join(spec.PrivateSubnetsCIDRs, "\",\""))) - - if spec.ClusterNetwork.ControlPlaneCIDR != "" { - buffer.WriteString(fmt.Sprintf("cluster_control_plane_cidr_block = \"%v\"\n", spec.ClusterNetwork.ControlPlaneCIDR)) - } - buffer.WriteString(fmt.Sprintf("cluster_subnetwork_cidr = \"%v\"\n", spec.ClusterNetwork.SubnetworkCIDR)) - buffer.WriteString(fmt.Sprintf("cluster_pod_subnetwork_cidr = \"%v\"\n", spec.ClusterNetwork.PodSubnetworkCIDR)) - buffer.WriteString(fmt.Sprintf("cluster_service_subnetwork_cidr = \"%v\"\n", spec.ClusterNetwork.ServiceSubnetworkCIDR)) - - buffer.WriteString(fmt.Sprintf("vpn_subnetwork_cidr = \"%v\"\n", spec.VPN.SubnetCIDR)) - if len(spec.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(spec.Tags) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) - } - buffer.WriteString(fmt.Sprintf("vpn_instances = %v\n", spec.VPN.Instances)) - if spec.VPN.Port != 0 { - buffer.WriteString(fmt.Sprintf("vpn_port = %v\n", spec.VPN.Port)) - } - if spec.VPN.InstanceType != "" { - buffer.WriteString(fmt.Sprintf("vpn_instance_type = \"%v\"\n", spec.VPN.InstanceType)) - } - if spec.VPN.DiskSize != 0 { - buffer.WriteString(fmt.Sprintf("vpn_instance_disk_size = %v\n", spec.VPN.DiskSize)) - } - if spec.VPN.OperatorName != "" { - buffer.WriteString(fmt.Sprintf("vpn_operator_name = \"%v\"\n", spec.VPN.OperatorName)) - } - if spec.VPN.DHParamsBits != 0 { - buffer.WriteString(fmt.Sprintf("vpn_dhparams_bits = %v\n", spec.VPN.DHParamsBits)) - } - if len(spec.VPN.OperatorCIDRs) != 0 { - buffer.WriteString(fmt.Sprintf("vpn_operator_cidrs = [\"%v\"]\n", strings.Join(spec.VPN.OperatorCIDRs, "\",\""))) - } - if len(spec.VPN.SSHUsers) != 0 { - buffer.WriteString(fmt.Sprintf("vpn_ssh_users = [\"%v\"]\n", strings.Join(spec.VPN.SSHUsers, "\",\""))) - } - - err = ioutil.WriteFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()), buffer.Bytes(), 0o600) - if err != nil { - return err - } - err = d.terraform.FormatWrite(context.Background(), tfexec.Dir(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) - if err != nil { - return err - } - return nil -} - -// New instantiates a new GCP provisioner -func New(config *configuration.Configuration) *GCP { - b := packr.New("GCP", projectPath) - return &GCP{ - box: b, - config: config, - } -} - -// SetTerraformExecutor adds the terraform executor to this provisioner -func (d *GCP) SetTerraformExecutor(tf *tfexec.Terraform) { - d.terraform = tf -} - -// TerraformExecutor returns the current terraform executor of this provisioner -func (d *GCP) TerraformExecutor() (tf *tfexec.Terraform) { - return d.terraform -} - -// Box returns the box that has the files as binary data -func (d GCP) Box() *packr.Box { - return d.box -} - -// TerraformFiles returns the list of files conforming the terraform project -func (d GCP) TerraformFiles() []string { - // TODO understand if it is possible to deduce these values somehow - // find . -type f -follow -print - return []string{ - "output.tf", - "main.tf", - "variables.tf", - } -} - -// Plan runs a dry run execution -func (d GCP) Plan() (err error) { - logrus.Info("[DRYRUN] Updating GCP Bootstrap project") - err = d.createVarFile() - if err != nil { - return err - } - changes, err := d.terraform.Plan(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) - if err != nil { - logrus.Fatalf("[DRYRUN] Something went wrong while updating gcp. %v", err) - return err - } - if changes { - logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") - } else { - logrus.Info("[DRYRUN] Everything is up to date") - } - - logrus.Info("[DRYRUN] GCP Updated") - return nil -} - -func (d GCP) Prepare() (err error) { - return nil -} - -// Update runs terraform apply in the project -func (d GCP) Update() (string, error) { - logrus.Info("Updating GCP Bootstrap project") - err := d.createVarFile() - if err != nil { - return "", err - } - - err = d.terraform.Apply(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) - if err != nil { - logrus.Fatalf("Something went wrong while updating gcp. %v", err) - return "", err - } - - logrus.Info("GCP Updated") - return "", nil -} - -// Destroy runs terraform destroy in the project -func (d GCP) Destroy() (err error) { - logrus.Info("Destroying GCP Bootstrap project") - err = d.createVarFile() - if err != nil { - return err - } - - err = d.terraform.Destroy(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/gcp.tfvars", d.terraform.WorkingDir()))) - if err != nil { - logrus.Fatalf("Something went wrong while destroying GCP Bootstrap project. %v", err) - return err - } - logrus.Info("GCP Bootstrap destroyed") - return nil -} diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index 3932ef6fd..b068351d9 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -17,7 +17,7 @@ import ( "github.com/sighupio/furyctl/internal/configuration" "github.com/sighupio/furyctl/internal/project" "github.com/sighupio/furyctl/internal/provisioners" - "github.com/sighupio/furyctl/pkg/terraform" + "github.com/sighupio/furyctl/internal/terraform" ) const initExecutorMessage = " Initializing the terraform executor" diff --git a/internal/cluster/configuration/gke.go b/internal/cluster/configuration/gke.go deleted file mode 100644 index bee9339fb..000000000 --- a/internal/cluster/configuration/gke.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -// GKE represents the configuration spec of a GKE Cluster -type GKE struct { - Version string `yaml:"version"` - Network string `yaml:"network"` - - NetworkProjectID string `yaml:"networkProjectID"` - ControlPlaneCIDR string `yaml:"controlPlaneCIDR"` - AdditionalFirewallRules bool `yaml:"additionalFirewallRules"` - AdditionalClusterFirewallRules bool `yaml:"additionalClusterFirewallRules"` - DisableDefaultSNAT bool `yaml:"disableDefaultSNAT"` - - SubNetworks []string `yaml:"subnetworks"` - DMZCIDRRange DMZCIDRRange `yaml:"dmzCIDRRange"` - SSHPublicKey string `yaml:"sshPublicKey"` - NodePools []GKENodePool `yaml:"nodePools"` - Tags map[string]string `yaml:"tags"` - Region string `yaml:"region"` - Project string `yaml:"project"` -} - -// GKENodePool represent a node pool configuration -type GKENodePool struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - MinSize int `yaml:"minSize"` - MaxSize int `yaml:"maxSize"` - InstanceType string `yaml:"instanceType"` - OS string `yaml:"os"` - MaxPods int `yaml:"maxPods"` - SpotInstance bool `yaml:"spotInstance"` - VolumeSize int `yaml:"volumeSize"` - Labels map[string]string `yaml:"labels"` - Taints []string `yaml:"taints"` - SubNetworks []string `yaml:"subnetworks"` - Tags map[string]string `yaml:"tags"` - AdditionalFirewallRules []GKENodePoolFwRule `yaml:"additionalFirewallRules"` -} - -// GKENodePoolFwRule represent an additional firewall rule to add to a specific node pool in the cluster -type GKENodePoolFwRule struct { - Name string `yaml:"name"` - Direction string `yaml:"direction"` - CIDRBlock string `yaml:"cidrBlock"` - Protocol string `yaml:"protocol"` - Ports string `yaml:"ports"` - Tags map[string]string `yaml:"tags"` -} diff --git a/internal/cluster/configuration/vsphere.go b/internal/cluster/configuration/vsphere.go deleted file mode 100644 index 6ae86f79d..000000000 --- a/internal/cluster/configuration/vsphere.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package configuration TODO -package configuration - -// VSphere represents the configuration spec of a VSphere Cluster -type VSphere struct { - Version string `yaml:"version"` - ControlPlaneEndpoint string `yaml:"controlPlaneEndpoint"` - ETCDConfig VSphereETCDConfig `yaml:"etcd"` - OIDCConfig VSphereOIDCConfig `yaml:"oidc"` - CRIConfig VSphereCRIConfig `yaml:"cri"` - - EnvironmentName string `yaml:"environmentName"` - Config VSphereConfig `yaml:"config"` - - NetworkConfig VSphereNetworkConfig `yaml:"networkConfig"` - - LoadBalancerNode VSphereKubeLoadBalancer `yaml:"lbNode"` - MasterNode VSphereKubeNode `yaml:"masterNode"` - InfraNode VSphereKubeNode `yaml:"infraNode"` - NodePools []VSphereKubeNode `yaml:"nodePools"` - - ClusterPODCIDR string `yaml:"clusterPODCIDR"` - ClusterSVCCIDR string `yaml:"clusterSVCCIDR"` - ClusterCIDR string `yaml:"clusterCIDR"` - SSHPublicKey []string `yaml:"sshPublicKeys"` -} - -type VSphereETCDConfig struct { - Version string `yaml:"version"` -} - -type VSphereOIDCConfig struct { - IssuerURL string `yaml:"issuerURL"` - ClientID string `yaml:"clientID"` - CAFile string `yaml:"caFile"` -} - -type VSphereCRIConfig struct { - Version string `yaml:"version"` - DNS []string `yaml:"dns"` - Proxy string `yaml:"proxy"` - Mirrors []string `yaml:"mirrors"` -} - -type VSphereKubeLoadBalancer struct { - Count int `yaml:"count"` - Template string `yaml:"template"` - CustomScriptPath string `yaml:"customScriptPath"` -} - -type VSphereKubeNode struct { - Role string `yaml:"role"` - Count int `yaml:"count"` - CPU int `yaml:"cpu"` - MemSize int `yaml:"memSize"` - DiskSize int `yaml:"diskSize"` - Template string `yaml:"template"` - Labels map[string]string `yaml:"labels"` - Taints []string `yaml:"taints"` - CustomScriptPath string `yaml:"customScriptPath"` -} - -// TODO: can you do that? -// type VSphereKubeNodePool struct { -// role string `yaml:"role"` -// VSphereKubeNode -// } - -type VSphereNetworkConfig struct { - Name string `yaml:"name"` - Gateway string `yaml:"gateway"` - Nameservers []string `yaml:"nameservers"` - Domain string `yaml:"domain"` - IPOffset int `yaml:"ipOffset"` -} - -type VSphereConfig struct { - DatacenterName string `yaml:"datacenterName"` - Datastore string `yaml:"datastore"` - EsxiHost []string `yaml:"esxiHosts"` - Cluster string `yaml:"cluster"` -} diff --git a/internal/cluster/provisioners/gke/provisioner.go b/internal/cluster/provisioners/gke/provisioner.go deleted file mode 100644 index a68652559..000000000 --- a/internal/cluster/provisioners/gke/provisioner.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gke - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "strings" - - "github.com/gobuffalo/packr/v2" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - cfg "github.com/sighupio/furyctl/internal/cluster/configuration" - "github.com/sighupio/furyctl/internal/configuration" -) - -// InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated -func (e *GKE) InitMessage() string { - return `[GKE] Fury - -This provisioner creates a battle-tested Google Cloud GKE Kubernetes Cluster -with a private and production-grade setup. - -Requires to connect to a VPN server to deploy the cluster from this computer. -Use a bastion host (inside the GKE VPC) as an alternative method to deploy the cluster. - -The provisioner requires the following software installed: -- /bin/sh -- wget or curl -- gcloud -- kubectl -` -} - -// UpdateMessage return a custom provisioner message the user will see once the cluster is updated -func (e *GKE) UpdateMessage() string { - var output map[string]tfexec.OutputMeta - output, err := e.terraform.Output(context.Background()) - if err != nil { - logrus.Error("Can not get output values") - } - var clusterEndpoint, clusterOperatorName string - err = json.Unmarshal(output["cluster_endpoint"].Value, &clusterEndpoint) - if err != nil { - logrus.Error("Can not get `cluster_endpoint` value") - } - err = json.Unmarshal(output["operator_ssh_user"].Value, &clusterOperatorName) - if err != nil { - logrus.Error("Can not get `operator_ssh_user` value") - } - return fmt.Sprintf( - `[GKE] Fury - -All the cluster components are up to date. -GKE Kubernetes cluster ready. - -GKE Cluster Endpoint: %v -SSH Operator Name: %v - -Use the ssh %v username to access the GKE instances with the configured SSH key. -Discover the instances by running - -$ kubectl get nodes - -Then access by running: - -$ ssh %v@node-name-reported-by-kubectl-get-nodes - -`, clusterEndpoint, clusterOperatorName, clusterOperatorName, clusterOperatorName, - ) -} - -// DestroyMessage return a custom provisioner message the user will see once the cluster is destroyed -func (e *GKE) DestroyMessage() string { - return `[GKE] Fury -All cluster components were destroyed. -GKE control plane and workers went away. - -Had problems, contact us at sales@sighup.io. -` -} - -// Enterprise return a boolean indicating it is an enterprise provisioner -func (e *GKE) Enterprise() bool { - return false -} - -// GKE represents the GKE provisioner -type GKE struct { - terraform *tfexec.Terraform - box *packr.Box - config *configuration.Configuration -} - -const ( - projectPath = "../../../../data/provisioners/cluster/gke" -) - -func (e GKE) createVarFile() (err error) { - var buffer bytes.Buffer - spec := e.config.Spec.(cfg.GKE) - - buffer.WriteString(fmt.Sprintf("provider_region = \"%v\"\n", spec.Region)) - buffer.WriteString(fmt.Sprintf("provider_project = \"%v\"\n", spec.Project)) - - buffer.WriteString(fmt.Sprintf("cluster_name = \"%v\"\n", e.config.Metadata.Name)) - buffer.WriteString(fmt.Sprintf("cluster_version = \"%v\"\n", spec.Version)) - buffer.WriteString(fmt.Sprintf("network = \"%v\"\n", spec.Network)) - buffer.WriteString(fmt.Sprintf("subnetworks = [\"%v\"]\n", strings.Join(spec.SubNetworks, "\",\""))) - buffer.WriteString(fmt.Sprintf("dmz_cidr_range = [\"%v\"]\n", strings.Join(spec.DMZCIDRRange.Values, "\",\""))) - buffer.WriteString(fmt.Sprintf("ssh_public_key = \"%v\"\n", spec.SSHPublicKey)) - if len(spec.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(spec.Tags) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) - } - - if len(spec.NodePools) > 0 { - buffer.WriteString("node_pools = [\n") - for _, np := range spec.NodePools { - buffer.WriteString("{\n") - buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", np.Name)) - buffer.WriteString(fmt.Sprintf("version = \"%v\"\n", np.Version)) - buffer.WriteString(fmt.Sprintf("min_size = %v\n", np.MinSize)) - buffer.WriteString(fmt.Sprintf("max_size = %v\n", np.MaxSize)) - buffer.WriteString(fmt.Sprintf("instance_type = \"%v\"\n", np.InstanceType)) - if np.OS != "" { - buffer.WriteString(fmt.Sprintf("os = \"%v\"\n", np.OS)) - } - if np.MaxPods > 0 { - buffer.WriteString(fmt.Sprintf("max_pods = %v\n", np.MaxPods)) - } - buffer.WriteString(fmt.Sprintf("volume_size = %v\n", np.VolumeSize)) - buffer.WriteString(fmt.Sprintf("spot_instance = %v\n", np.SpotInstance)) - - if len(np.SubNetworks) > 0 { - buffer.WriteString(fmt.Sprintf("subnetworks = [\"%v\"]\n", strings.Join(np.SubNetworks, "\",\""))) - } else { - buffer.WriteString("subnetworks = []\n") - } - if len(np.Labels) > 0 { - var labels []byte - labels, err = json.Marshal(np.Labels) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("labels = %v\n", string(labels))) - } else { - buffer.WriteString("labels = {}\n") - } - - if len(np.Taints) > 0 { - buffer.WriteString(fmt.Sprintf("taints = [\"%v\"]\n", strings.Join(np.Taints, "\",\""))) - } else { - buffer.WriteString("taints = []\n") - } - - if len(np.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(np.Tags) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) - } else { - buffer.WriteString("tags = {}\n") - } - - if len(np.AdditionalFirewallRules) > 0 { - - buffer.WriteString("additional_firewall_rules = [\n") - for _, fwRule := range np.AdditionalFirewallRules { - - fwRuleTags := "{}" - if len(fwRule.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(fwRule.Tags) - if err != nil { - return err - } - fwRuleTags = string(tags) - } - - buffer.WriteString( - fmt.Sprintf( - `{ - name = "%v" - direction = "%v" - cidr_block = "%v" - protocol = "%v" - ports = "%v" - tags = %v - }, - `, fwRule.Name, fwRule.Direction, fwRule.CIDRBlock, fwRule.Protocol, fwRule.Ports, fwRuleTags, - ), - ) - } - buffer.WriteString("]\n") - } else { - buffer.WriteString("additional_firewall_rules = []\n") - } - - buffer.WriteString("},\n") - } - buffer.WriteString("]\n") - } - - buffer.WriteString(fmt.Sprintf("gke_network_project_id = \"%v\"\n", spec.NetworkProjectID)) - buffer.WriteString(fmt.Sprintf("gke_master_ipv4_cidr_block = \"%v\"\n", spec.ControlPlaneCIDR)) - buffer.WriteString(fmt.Sprintf("gke_add_additional_firewall_rules = %v\n", spec.AdditionalFirewallRules)) - buffer.WriteString(fmt.Sprintf("gke_add_cluster_firewall_rules = %v\n", spec.AdditionalClusterFirewallRules)) - buffer.WriteString(fmt.Sprintf("gke_disable_default_snat = %v\n", spec.DisableDefaultSNAT)) - - err = ioutil.WriteFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0o600) - if err != nil { - return err - } - err = e.terraform.FormatWrite( - context.Background(), - tfexec.Dir(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - return err - } - return nil -} - -// New instantiates a new GKE provisioner -func New(config *configuration.Configuration) *GKE { - b := packr.New("gkecluster", projectPath) - return &GKE{ - box: b, - config: config, - } -} - -// SetTerraformExecutor adds the terraform executor to this provisioner -func (e *GKE) SetTerraformExecutor(tf *tfexec.Terraform) { - e.terraform = tf -} - -// TerraformExecutor returns the current terraform executor of this provisioner -func (e *GKE) TerraformExecutor() (tf *tfexec.Terraform) { - return e.terraform -} - -// Box returns the box that has the files as binary data -func (e GKE) Box() *packr.Box { - return e.box -} - -// TerraformFiles returns the list of files conforming the terraform project -func (e GKE) TerraformFiles() []string { - // TODO understand if it is possible to deduce these values somehow - // find . -type f -follow -print - return []string{ - "output.tf", - "main.tf", - "variables.tf", - } -} - -// Plan runs a dry run execution -func (e GKE) Plan() (err error) { - logrus.Info("[DRYRUN] Updating GKE Cluster project") - err = e.createVarFile() - if err != nil { - return err - } - var changes bool - changes, err = e.terraform.Plan( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("[DRYRUN] Something went wrong while updating gke. %v", err) - return err - } - if changes { - logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") - } else { - logrus.Info("[DRYRUN] Everything is up to date") - } - - logrus.Info("[DRYRUN] GKE Updated") - return nil -} - -func (e GKE) Prepare() (err error) { - return nil -} - -// Update runs terraform apply in the project -func (e GKE) Update() (string, error) { - logrus.Info("Updating GKE project") - err := e.createVarFile() - if err != nil { - return "", err - } - err = e.terraform.Apply( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("Something went wrong while updating gke. %v", err) - return "", err - } - - logrus.Info("GKE Updated") - return e.kubeconfig() -} - -// Destroy runs terraform destroy in the project -func (e GKE) Destroy() (err error) { - logrus.Info("Destroying GKE project") - err = e.createVarFile() - if err != nil { - return err - } - err = e.terraform.Destroy( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/gke.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("Something went wrong while destroying GKE cluster project. %v", err) - return err - } - logrus.Info("GKE destroyed") - return nil -} - -func (e GKE) kubeconfig() (string, error) { - logrus.Info("Gathering output file as json") - var output map[string]tfexec.OutputMeta - output, err := e.terraform.Output(context.Background()) - if err != nil { - logrus.Fatalf("Error while getting project output: %v", err) - return "", err - } - var creds string - err = json.Unmarshal(output["kubeconfig"].Value, &creds) - if err != nil { - logrus.Fatalf("Error while tranforming the kubeconfig value into string: %v", err) - return "", err - } - return creds, nil -} diff --git a/internal/cluster/provisioners/vsphere/provisioner.go b/internal/cluster/provisioners/vsphere/provisioner.go deleted file mode 100644 index 003efa57b..000000000 --- a/internal/cluster/provisioners/vsphere/provisioner.go +++ /dev/null @@ -1,557 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package vsphere - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/gobuffalo/packr/v2" - getter "github.com/hashicorp/go-getter" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/relex/aini" - "github.com/sirupsen/logrus" - - cfg "github.com/sighupio/furyctl/internal/cluster/configuration" - "github.com/sighupio/furyctl/internal/configuration" -) - -// VSphere represents the VSphere provisioner -type VSphere struct { - terraform *tfexec.Terraform - box *packr.Box - config *configuration.Configuration -} - -// InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated -func (e *VSphere) InitMessage() string { - return `[VSphere] Fury - -This provisioner creates a battle-tested Kubernetes vSphere Cluster -with a private and production-grade setup. - -It will deploy all the components required to run a Kubernetes Cluster: -- Load Balancer (Control Plane & Infrastructure components) -- Kubernetes Control Plane -- Dedicated intrastructure nodes -- General node pools - -Requires to connect to a VPN server to deploy the cluster from this computer. -Use a bastion host (inside the same vSphere network) as an alternative method to deploy the cluster. - -The provisioner requires the following software installed: -- ansible - -And internet connection to download remote repositories from the SIGHUP enterprise repositories. -` -} - -// UpdateMessage return a custom provisioner message the user will see once the cluster is updated -func (e *VSphere) UpdateMessage() string { - var output map[string]tfexec.OutputMeta - output, err := e.terraform.Output(context.Background()) - if err != nil { - logrus.Error("Can not get output values") - } - var inventoryOutput string - err = json.Unmarshal(output["ansible_inventory"].Value, &inventoryOutput) - if err != nil { - logrus.Error("Can not get `ansible_inventory` value") - } - inventory, _ := aini.Parse(strings.NewReader(inventoryOutput)) - kubernetes_control_plane_address := strings.Replace( - inventory.Groups["all"].Vars["kubernetes_control_plane_address"], - "\"", - "", - -1, - ) - clusterOperatorName := strings.Replace(inventory.Groups["all"].Vars["ansible_user"], "\"", "", -1) - - return fmt.Sprintf( - `[vSphere] Fury - -All the cluster components are up to date. -vSphere Kubernetes cluster ready. - -vSphere Cluster Endpoint: %v -SSH Operator Name: %v - -Use the ssh %v username to access the vSphere instances with the configured SSH key. -Discover the instances by running - -$ kubectl get nodes - -Then access by running: - -$ ssh %v@node-name-reported-by-kubectl-get-nodes - -`, kubernetes_control_plane_address, clusterOperatorName, clusterOperatorName, clusterOperatorName, - ) -} - -// DestroyMessage return a custom provisioner message the user will see once the cluster is destroyed -func (e *VSphere) DestroyMessage() string { - return `[VSphere] Fury -All cluster components were destroyed. -vSphere control plane, load balancer and workers went away. - -Had problems, contact us at sales@sighup.io. -` -} - -// Enterprise return a boolean indicating it is an enterprise provisioner -func (e *VSphere) Enterprise() bool { - return true -} - -const ( - projectPath = "../../../../data/provisioners/cluster/vsphere" -) - -func (e VSphere) createVarFile() (err error) { - var buffer bytes.Buffer - spec := e.config.Spec.(cfg.VSphere) - buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", e.config.Metadata.Name)) - buffer.WriteString(fmt.Sprintf("kube_version = \"%v\"\n", spec.Version)) - buffer.WriteString(fmt.Sprintf("kube_control_plane_endpoint = \"%v\"\n", spec.ControlPlaneEndpoint)) - if spec.ETCDConfig.Version != "" { - buffer.WriteString(fmt.Sprintf("etcd_version = \"%v\"\n", spec.ETCDConfig.Version)) - } - if spec.OIDCConfig.IssuerURL != "" { - buffer.WriteString(fmt.Sprintf("oidc_issuer_url = \"%v\"\n", spec.OIDCConfig.IssuerURL)) - } - if spec.OIDCConfig.ClientID != "" { - buffer.WriteString(fmt.Sprintf("oidc_client_id = \"%v\"\n", spec.OIDCConfig.ClientID)) - } - if spec.OIDCConfig.CAFile != "" { - buffer.WriteString(fmt.Sprintf("oidc_ca_file = \"%v\"\n", spec.OIDCConfig.CAFile)) - } - - if spec.CRIConfig.Version != "" { - buffer.WriteString(fmt.Sprintf("cri_version = \"%v\"\n", spec.CRIConfig.Version)) - } - if spec.CRIConfig.Proxy != "" { - buffer.WriteString(fmt.Sprintf("cri_proxy = \"%v\"\n", spec.CRIConfig.Proxy)) - } - if len(spec.CRIConfig.DNS) > 0 { - buffer.WriteString(fmt.Sprintf("cri_dns = [\"%v\"]\n", strings.Join(spec.CRIConfig.DNS, "\",\""))) - } - if len(spec.CRIConfig.Mirrors) > 0 { - buffer.WriteString(fmt.Sprintf("cri_mirrors = [\"%v\"]\n", strings.Join(spec.CRIConfig.Mirrors, "\",\""))) - } - - buffer.WriteString(fmt.Sprintf("env = \"%v\"\n", spec.EnvironmentName)) - buffer.WriteString(fmt.Sprintf("datacenter = \"%v\"\n", spec.Config.DatacenterName)) - buffer.WriteString(fmt.Sprintf("vsphere_cluster = \"%v\"\n", spec.Config.Cluster)) - if len(spec.Config.EsxiHost) > 0 { - buffer.WriteString(fmt.Sprintf("esxihosts = [\"%v\"]\n", strings.Join(spec.Config.EsxiHost, "\",\""))) - } else { - buffer.WriteString("esxihosts = []\n") - } - buffer.WriteString(fmt.Sprintf("datastore = \"%v\"\n", spec.Config.Datastore)) - buffer.WriteString(fmt.Sprintf("network = \"%v\"\n", spec.NetworkConfig.Name)) - buffer.WriteString(fmt.Sprintf("net_cidr = \"%v\"\n", spec.ClusterCIDR)) - buffer.WriteString(fmt.Sprintf("net_gateway = \"%v\"\n", spec.NetworkConfig.Gateway)) - buffer.WriteString( - fmt.Sprintf( - "net_nameservers = [\"%v\"]\n", - strings.Join(spec.NetworkConfig.Nameservers, "\",\""), - ), - ) - buffer.WriteString(fmt.Sprintf("net_domain = \"%v\"\n", spec.NetworkConfig.Domain)) - buffer.WriteString(fmt.Sprintf("ip_offset = %v\n", spec.NetworkConfig.IPOffset)) - if len(spec.SSHPublicKey) > 0 { - buffer.WriteString(fmt.Sprintf("ssh_public_keys = [\"%v\"]\n", strings.Join(spec.SSHPublicKey, "\",\""))) - } else { - buffer.WriteString("ssh_public_keys = []\n") - } - buffer.WriteString(fmt.Sprintf("kube_lb_count = %v\n", spec.LoadBalancerNode.Count)) - buffer.WriteString(fmt.Sprintf("kube_lb_template = \"%v\"\n", spec.LoadBalancerNode.Template)) - buffer.WriteString(fmt.Sprintf("kube_lb_custom_script_path = \"%v\"\n", spec.LoadBalancerNode.CustomScriptPath)) - buffer.WriteString(fmt.Sprintf("kube_master_count = %v\n", spec.MasterNode.Count)) - buffer.WriteString(fmt.Sprintf("kube_master_cpu = %v\n", spec.MasterNode.CPU)) - buffer.WriteString(fmt.Sprintf("kube_master_mem = %v\n", spec.MasterNode.MemSize)) - buffer.WriteString(fmt.Sprintf("kube_master_disk_size = %v\n", spec.MasterNode.DiskSize)) - buffer.WriteString(fmt.Sprintf("kube_master_template = \"%v\"\n", spec.MasterNode.Template)) - // TODO: restore - if len(spec.MasterNode.Labels) > 0 { - var labels []byte - labels, err = json.Marshal(spec.MasterNode.Labels) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("kube_master_labels = %v\n", string(labels))) - } else { - buffer.WriteString("kube_master_labels = {}\n") - } - if len(spec.MasterNode.Taints) > 0 { - buffer.WriteString( - fmt.Sprintf( - "kube_master_taints = [\"%v\"]\n", - strings.Join(spec.MasterNode.Taints, "\",\""), - ), - ) - } else { - buffer.WriteString("kube_master_taints = []\n") - } - buffer.WriteString(fmt.Sprintf("kube_master_custom_script_path = \"%v\"\n", spec.MasterNode.CustomScriptPath)) - - buffer.WriteString(fmt.Sprintf("kube_pod_cidr = \"%v\"\n", spec.ClusterPODCIDR)) - buffer.WriteString(fmt.Sprintf("kube_svc_cidr = \"%v\"\n", spec.ClusterSVCCIDR)) - - buffer.WriteString(fmt.Sprintf("kube_infra_count = %v\n", spec.InfraNode.Count)) - buffer.WriteString(fmt.Sprintf("kube_infra_cpu = %v\n", spec.InfraNode.CPU)) - buffer.WriteString(fmt.Sprintf("kube_infra_mem = %v\n", spec.InfraNode.MemSize)) - buffer.WriteString(fmt.Sprintf("kube_infra_disk_size = %v\n", spec.InfraNode.DiskSize)) - buffer.WriteString(fmt.Sprintf("kube_infra_template = \"%v\"\n", spec.InfraNode.Template)) - if len(spec.InfraNode.Labels) > 0 { - var labels []byte - labels, err = json.Marshal(spec.InfraNode.Labels) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("kube_infra_labels = %v\n", string(labels))) - } else { - buffer.WriteString("kube_infra_labels = {}\n") - } - if len(spec.InfraNode.Taints) > 0 { - buffer.WriteString(fmt.Sprintf("kube_infra_taints = [\"%v\"]\n", strings.Join(spec.InfraNode.Taints, "\",\""))) - } else { - buffer.WriteString("kube_infra_taints = []\n") - } - buffer.WriteString(fmt.Sprintf("kube_infra_custom_script_path = \"%v\"\n", spec.InfraNode.CustomScriptPath)) - - if len(spec.NodePools) > 0 { - buffer.WriteString("node_pools = [\n") - for _, np := range spec.NodePools { - buffer.WriteString("{\n") - buffer.WriteString(fmt.Sprintf("role = \"%v\"\n", np.Role)) - buffer.WriteString(fmt.Sprintf("template = \"%v\"\n", np.Template)) - buffer.WriteString(fmt.Sprintf("count = %v\n", np.Count)) - buffer.WriteString(fmt.Sprintf("memory = %v\n", np.MemSize)) - buffer.WriteString(fmt.Sprintf("cpu = %v\n", np.CPU)) - buffer.WriteString(fmt.Sprintf("disk_size = %v\n", np.DiskSize)) - if len(np.Labels) > 0 { - var labels []byte - labels, err = json.Marshal(np.Labels) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("labels = %v\n", string(labels))) - } else { - buffer.WriteString("labels = {}\n") - } - if len(np.Taints) > 0 { - buffer.WriteString(fmt.Sprintf("taints = [\"%v\"]\n", strings.Join(np.Taints, "\",\""))) - } else { - buffer.WriteString("taints = []\n") - } - // TODO: restore - buffer.WriteString(fmt.Sprintf("custom_script_path = \"%v\"\n", "")) - buffer.WriteString("},\n") - } - buffer.WriteString("]\n") - } - - err = ioutil.WriteFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0o600) - if err != nil { - return err - } - err = e.terraform.FormatWrite( - context.Background(), - tfexec.Dir(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - return err - } - return nil -} - -// New instantiates a new vSphere provisioner -func New(config *configuration.Configuration) *VSphere { - b := packr.New("vsphereCluster", projectPath) - return &VSphere{ - box: b, - config: config, - } -} - -// SetTerraformExecutor adds the terraform executor to this provisioner -func (e *VSphere) SetTerraformExecutor(tf *tfexec.Terraform) { - e.terraform = tf -} - -// TerraformExecutor returns the current terraform executor of this provisioner -func (e *VSphere) TerraformExecutor() (tf *tfexec.Terraform) { - return e.terraform -} - -// Box returns the box that has the files as binary data -func (e VSphere) Box() *packr.Box { - return e.box -} - -// TODO: find Terraform files -// TODO: find Ansible files -// TODO: rename method TerraformFiles() in FilesToBudle() - -// TerraformFiles returns the list of files conforming the terraform project -func (e VSphere) TerraformFiles() []string { - // TODO understand if it is possible to deduce these values somehow - // find . -type f -follow -print - return []string{ - "output.tf", - "main.tf", - "variables.tf", - "provision/ansible.cfg", - "provision/all-in-one.yml", - "furyagent/furyagent.yml", - } -} - -// Prepare the environment before running anything -func (e VSphere) Prepare() error { - if err := createPKI(e.terraform.WorkingDir()); err != nil { - return fmt.Errorf("error creating PKI: %v", err) - } - - if err := downloadAnsibleRoles(e.terraform.WorkingDir()); err != nil { - return fmt.Errorf("error downloading Ansible roles: %v", err) - } - - return nil -} - -func downloadAnsibleRoles(workingDirectory string) error { - p_netrc := os.Getenv("NETRC") - defer os.Setenv("NETRC", p_netrc) - - netrcpath := filepath.Join(workingDirectory, "configuration/.netrc") - logrus.Infof("Configuring the NETRC environment variable: %v", netrcpath) - os.Setenv("NETRC", netrcpath) - - downloadPath := filepath.Join(workingDirectory, "provision/roles") - logrus.Infof("Ansible roles download path: %v", downloadPath) - if err := os.Mkdir(downloadPath, 0o755); err != nil { - return err - } - - client := &getter.Client{ - Src: "https://github.com/sighupio/furyctl-provisioners/archive/refs/tags/v0.7.0.zip//furyctl-provisioners-0.7.0/roles", - Dst: downloadPath, - Pwd: workingDirectory, - Mode: getter.ClientModeAny, - } - - return client.Get() -} - -// Plan runs a dry run execution -func (e VSphere) Plan() (err error) { - logrus.Info("[DRYRUN] Updating VSphere Cluster project") - // TODO: give the name of the file - err = e.createVarFile() - if err != nil { - return err - } - var changes bool - changes, err = e.terraform.Plan( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("[DRYRUN] Something went wrong while updating vsphere. %v", err) - return err - } - if changes { - logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") - } else { - logrus.Info("[DRYRUN] Everything is up to date") - } - - logrus.Info("[DRYRUN] VSphere Updated") - return nil -} - -// Update runs terraform apply in the project -func (e VSphere) Update() (string, error) { - logrus.Info("Updating VSphere project") - err := e.createVarFile() - if err != nil { - return "", err - } - err = e.terraform.Apply( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("Something went wrong while updating vsphere. %v", err) - return "", err - } - - var output map[string]tfexec.OutputMeta - output, err = e.terraform.Output(context.Background()) - if err != nil { - logrus.Error("Can not get output values") - return "", err - } - - var ansibleInventory, haproxyConfig string - err = json.Unmarshal(output["ansible_inventory"].Value, &ansibleInventory) - if err != nil { - logrus.Error("Can not get `ansible_inventory` value") - return "", err - } - err = json.Unmarshal(output["haproxy_config"].Value, &haproxyConfig) - if err != nil { - logrus.Error("Can not get `haproxy_config` value") - return "", err - } - - filePath := filepath.Join(e.terraform.WorkingDir(), "provision/hosts.ini") - err = ioutil.WriteFile(filePath, []byte(ansibleInventory), 0o644) - if err != nil { - return "", err - } - - filePath = filepath.Join(e.terraform.WorkingDir(), "provision/haproxy.cfg") - err = ioutil.WriteFile(filePath, []byte(haproxyConfig), 0o644) - if err != nil { - return "", err - } - - kubeconfig, err := runAnsiblePlaybook( - filepath.Join(e.terraform.WorkingDir(), "provision"), - filepath.Join(e.terraform.WorkingDir(), "logs"), - ) - log.Info("VSphere Updated") - return kubeconfig, err -} - -// Destroy runs terraform destroy in the project -func (e VSphere) Destroy() (err error) { - logrus.Info("Destroying VSphere project") - err = e.createVarFile() - if err != nil { - return err - } - err = e.terraform.Destroy( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/vsphere.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("Something went wrong while destroying VSphere cluster project. %v", err) - return err - } - logrus.Info("VSphere destroyed") - return nil -} - -func createPKI(workingDirectory string) error { - source := fmt.Sprintf( - "https://github.com/sighupio/furyagent/releases/download/v0.3.0/furyagent-%s-%s", - runtime.GOOS, - runtime.GOARCH, - ) - downloadPath := filepath.Join(workingDirectory, "furyagent") - - logrus.Infof("Download furyagent: %v", downloadPath) - - if err := os.MkdirAll(downloadPath, 0o755); err != nil { - return err - } - - client := &getter.Client{ - Src: source, - Dst: downloadPath, - Pwd: workingDirectory, - Mode: getter.ClientModeAny, - } - if err := client.Get(); err != nil { - return err - } - - tokens := strings.Split(source, "/") - downloadedExecutableName := tokens[len(tokens)-1] - wantedExecutableName := "furyagent" - - if err := os.Rename( - filepath.Join(downloadPath, downloadedExecutableName), - filepath.Join(downloadPath, wantedExecutableName), - ); err != nil { - logrus.Fatal(err) - } - - os.Chmod(filepath.Join(downloadPath, wantedExecutableName), 0o755) - - cmd := exec.Command("./furyagent", "init", "master") - cmd.Dir = downloadPath - out, err := cmd.Output() - if err != nil { - logrus.Debugf("%s", out) - logrus.Fatal(err) - } - - cmd = exec.Command("./furyagent", "init", "etcd") - cmd.Dir = downloadPath - out, err = cmd.Output() - if err != nil { - logrus.Debugf("%s", out) - logrus.Fatal(err) - } - - return nil -} - -func runAnsiblePlaybook(workingDir, logDir string) (string, error) { - logrus.Infof("Run Ansible playbook in : %v", workingDir) - - // TODO: Get the debug flag from the CLI to output both to a file and stdout - // open the log file for writing - logFilePath := filepath.Join(logDir, "ansible.log") - logFile, err := os.Create(logFilePath) - if err != nil { - logrus.Errorf("Can not open the log file %v", logFilePath) - return "", err - } - defer logFile.Close() - - cmd := exec.Command("ansible", "--version") - cmd.Dir = workingDir - cmd.Stdout = logFile - cmd.Stderr = logFile - err = cmd.Run() - if err != nil { - logrus.Debug("Please make sure you have Ansible installed in this machine") - logrus.Fatal(err) - return "", err - } - - cmd = exec.Command("ansible-playbook", "all-in-one.yml") - cmd.Dir = workingDir - cmd.Stdout = logFile - cmd.Stderr = logFile - err = cmd.Start() - if err != nil { - logrus.Fatal(err) - return "", err - } - err = cmd.Wait() - if err != nil { - logrus.Fatal(err) - return "", err - } - - dat, err := ioutil.ReadFile(filepath.Join(workingDir, "../secrets/users/admin.conf")) - return string(dat), err -} diff --git a/internal/configuration/config.go b/internal/configuration/config.go index fcc2de275..2c7bec173 100644 --- a/internal/configuration/config.go +++ b/internal/configuration/config.go @@ -94,35 +94,6 @@ func clusterParser(config *Configuration) (err error) { } config.Spec = eksSpec return nil - case provisioner == "gke": - gkeSpec := clustercfg.GKE{ - NetworkProjectID: "", - ControlPlaneCIDR: "10.0.0.0/28", - AdditionalFirewallRules: true, - AdditionalClusterFirewallRules: false, - DisableDefaultSNAT: false, - } - err = yaml.Unmarshal(specBytes, &gkeSpec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - config.Spec = gkeSpec - return nil - case provisioner == "vsphere": - vsphereSpec := clustercfg.VSphere{ - NetworkConfig: clustercfg.VSphereNetworkConfig{ - Domain: "localdomain", - IPOffset: 0, - }, - } - err = yaml.Unmarshal(specBytes, &vsphereSpec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - config.Spec = vsphereSpec - return nil default: logrus.Error("Error parsing the configuration file. Provisioner not found") return errors.New("cluster provisioner not found") @@ -151,19 +122,6 @@ func bootstrapParser(config *Configuration) (err error) { } config.Spec = awsSpec return nil - case provisioner == "gcp": - gcpSpec := bootstrapcfg.GCP{ - VPN: bootstrapcfg.GCPVPN{ - Instances: 1, - }, - } - err = yaml.Unmarshal(specBytes, &gcpSpec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - config.Spec = gcpSpec - return nil default: logrus.Error("Error parsing the configuration file. Provisioner not found") return errors.New("bootstrap provisioner not found") diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go index eaa9ea4e1..4d0db9c06 100644 --- a/internal/configuration/templates.go +++ b/internal/configuration/templates.go @@ -83,32 +83,6 @@ func bootstrapTemplate(config *Configuration) error { }, } config.Spec = spec - case config.Provisioner == "gcp": - spec := bootstrapcfg.GCP{ - PublicSubnetsCIDRs: []string{"Required", "10.0.1.0/24", "10.0.2.0/24"}, - PrivateSubnetsCIDRs: []string{"Required", "10.0.11.0/24", "10.0.12.0/24"}, - ClusterNetwork: bootstrapcfg.GCPClusterNetwork{ - ControlPlaneCIDR: "10.0.0.0/28 # Optional. Control Plane CIDR. This value is the default one", - SubnetworkCIDR: "10.1.0.0/16 # Required. Cluster nodes subnetwork", - PodSubnetworkCIDR: "10.2.0.0/16 # Required. Pod subnetwork CIDR", - ServiceSubnetworkCIDR: "10.3.0.0/16 # Required. Service subnetwork CIDR", - }, - VPN: bootstrapcfg.GCPVPN{ - Instances: 2, - Port: 1194, - InstanceType: "n1-standard-1 # This is the default value. Specify any GCP instance type", - DiskSize: 50, - OperatorName: "sighup # This is the default value. SSH User to access the instance", - DHParamsBits: 2048, - SubnetCIDR: "172.16.0.0/16 # Required attribute. Should be different from the networkCIDR", - SSHUsers: []string{"Required", "angelbarrera92", "jnardiello"}, - OperatorCIDRs: []string{"0.0.0.0/0", "This is the default value"}, - }, - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate all resources. Optional", - }, - } - config.Spec = spec default: log.Errorf( "Error creating a template configuration file. Parser not found for %v provisioner", diff --git a/internal/io/fs.go b/internal/io/fs.go index e13c4758a..8d43f970c 100644 --- a/internal/io/fs.go +++ b/internal/io/fs.go @@ -94,3 +94,16 @@ func CopyFromSourceToTarget(src, dst string) (int64, error) { return io.Copy(destination, source) } + +// EnsureDir creates the directories to host the file. +// Example: hello/world.md will create the hello dir if it does not exists. +func EnsureDir(fileName string) (err error) { + dirName := filepath.Dir(fileName) + if _, serr := os.Stat(dirName); serr != nil { + err := os.MkdirAll(dirName, os.ModePerm) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/project/project.go b/internal/project/project.go index 0f1b127cc..936330a17 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -12,7 +12,7 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/furyctl/pkg/utils" + "github.com/sighupio/furyctl/internal/io" ) const ( @@ -63,7 +63,7 @@ func (p *Project) CreateSubDirs(subDirs []string) (err error) { // WriteFile writes a new file (fileName) with the content specified func (p *Project) WriteFile(fileName string, content []byte) (err error) { filePath := fmt.Sprintf("%v/%v", p.Path, fileName) - err = utils.EnsureDir(filePath) + err = io.EnsureDir(filePath) if err != nil { return err } diff --git a/internal/provisioners/provisioners.go b/internal/provisioners/provisioners.go index bf526f1b2..ed6c87eb2 100644 --- a/internal/provisioners/provisioners.go +++ b/internal/provisioners/provisioners.go @@ -14,10 +14,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/furyctl/internal/bootstrap/provisioners/aws" - "github.com/sighupio/furyctl/internal/bootstrap/provisioners/gcp" "github.com/sighupio/furyctl/internal/cluster/provisioners/eks" - "github.com/sighupio/furyctl/internal/cluster/provisioners/gke" - "github.com/sighupio/furyctl/internal/cluster/provisioners/vsphere" "github.com/sighupio/furyctl/internal/configuration" ) @@ -58,10 +55,6 @@ func getClusterProvisioner(config configuration.Configuration) (Provisioner, err switch { case config.Provisioner == "eks": return eks.New(&config), nil - case config.Provisioner == "gke": - return gke.New(&config), nil - case config.Provisioner == "vsphere": - return vsphere.New(&config), nil default: logrus.Error("Provisioner not found") return nil, errors.New("Provisioner not found") @@ -72,8 +65,6 @@ func getBootstrapProvisioner(config configuration.Configuration) (Provisioner, e switch { case config.Provisioner == "aws": return aws.New(&config), nil - case config.Provisioner == "gcp": - return gcp.New(&config), nil default: logrus.Error("Provisioner not found") return nil, errors.New("Provisioner not found") diff --git a/internal/template/funcmap.go b/internal/template/funcmap.go index 361af7282..c88bd0a0f 100644 --- a/internal/template/funcmap.go +++ b/internal/template/funcmap.go @@ -5,9 +5,11 @@ package template import ( + "strings" "text/template" "github.com/Masterminds/sprig/v3" + "gopkg.in/yaml.v2" ) type FuncMap struct { @@ -25,3 +27,21 @@ func (f *FuncMap) Add(name string, fn interface{}) { func (f *FuncMap) Delete(name string) { delete(f.FuncMap, name) } + +func toYAML(v any) string { + data, err := yaml.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return strings.TrimSuffix(string(data), "\n") +} + +func fromYAML(str string) map[string]any { + m := map[string]any{} + + if err := yaml.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} diff --git a/internal/template/model.go b/internal/template/model.go index dc2c83d11..8364576b8 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -17,7 +17,6 @@ import ( "github.com/sighupio/furyctl/internal/io" "github.com/sighupio/furyctl/internal/template/mapper" yaml2 "github.com/sighupio/furyctl/internal/yaml" - fTemplate "github.com/sighupio/furyctl/pkg/template" ) type Model struct { @@ -71,8 +70,8 @@ func NewTemplateModel( } funcMap := NewFuncMap() - funcMap.Add("toYaml", fTemplate.ToYAML) - funcMap.Add("fromYaml", fTemplate.FromYAML) + funcMap.Add("toYaml", toYAML) + funcMap.Add("fromYaml", fromYAML) return &Model{ SourcePath: source, diff --git a/pkg/terraform/install.go b/internal/terraform/install.go similarity index 95% rename from pkg/terraform/install.go rename to internal/terraform/install.go index 8f15f464d..b3ba0cdbd 100644 --- a/pkg/terraform/install.go +++ b/internal/terraform/install.go @@ -17,7 +17,7 @@ import ( "github.com/hashicorp/terraform-exec/tfinstall" "github.com/sirupsen/logrus" - "github.com/sighupio/furyctl/pkg/utils" + "github.com/sighupio/furyctl/internal/io" ) // ensure ensures a working terraform version to be used in the project @@ -82,7 +82,7 @@ func alreadyAvailable(terraformVersion, terraformDownloadPath string) (bool, str func install(terraformVersion, terraformDownloadPath string) (binPath string, err error) { ready, binPath := alreadyAvailable(terraformVersion, terraformDownloadPath) if !ready { - err := utils.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) + err := io.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) if err != nil { return "", err } @@ -102,7 +102,7 @@ func installLatest(terraformDownloadPath string) (binPath string, err error) { } ready, binPath := alreadyAvailable(terraformVersion, terraformDownloadPath) if !ready { - err := utils.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) + err := io.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) if err != nil { return "", err } diff --git a/pkg/terraform/terraform.go b/internal/terraform/terraform.go similarity index 100% rename from pkg/terraform/terraform.go rename to internal/terraform/terraform.go diff --git a/pkg/template/funcmap.go b/pkg/template/funcmap.go deleted file mode 100644 index e760d3880..000000000 --- a/pkg/template/funcmap.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "strings" - - "gopkg.in/yaml.v2" -) - -func ToYAML(v any) string { - data, err := yaml.Marshal(v) - if err != nil { - // Swallow errors inside of a template. - return "" - } - return strings.TrimSuffix(string(data), "\n") -} - -func FromYAML(str string) map[string]any { - m := map[string]any{} - - if err := yaml.Unmarshal([]byte(str), &m); err != nil { - m["Error"] = err.Error() - } - return m -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go deleted file mode 100644 index 704b720c3..000000000 --- a/pkg/utils/utils.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package utils - -import ( - "fmt" - "io" - "os" - "path/filepath" -) - -// CopyFile copies a file (src) to a new destination (dst) -func CopyFile(src, dst string) (int64, error) { - sourceFileStat, err := os.Stat(src) - if err != nil { - return 0, err - } - - if !sourceFileStat.Mode().IsRegular() { - return 0, fmt.Errorf("%s is not a regular file", src) - } - - source, err := os.Open(src) - if err != nil { - return 0, err - } - defer source.Close() - - destination, err := os.Create(dst) - if err != nil { - return 0, err - } - defer destination.Close() - nBytes, err := io.Copy(destination, source) - return nBytes, err -} - -// EnsureDir creates the directories to host the file. -// Example: hello/world.md will create the hello dir if it does not exists. -func EnsureDir(fileName string) (err error) { - dirName := filepath.Dir(fileName) - if _, serr := os.Stat(dirName); serr != nil { - err := os.MkdirAll(dirName, os.ModePerm) - if err != nil { - return err - } - } - return nil -} diff --git a/automated-tests/e2e-tests/aws-eks/.gitignore b/test/e2e/aws-eks/.gitignore similarity index 100% rename from automated-tests/e2e-tests/aws-eks/.gitignore rename to test/e2e/aws-eks/.gitignore diff --git a/automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml b/test/e2e/aws-eks/bootstrap.tpl.yml similarity index 100% rename from automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml rename to test/e2e/aws-eks/bootstrap.tpl.yml diff --git a/automated-tests/e2e-tests/aws-eks/cluster.tpl.yml b/test/e2e/aws-eks/cluster.tpl.yml similarity index 100% rename from automated-tests/e2e-tests/aws-eks/cluster.tpl.yml rename to test/e2e/aws-eks/cluster.tpl.yml diff --git a/automated-tests/e2e-tests/aws-eks/tests.sh b/test/e2e/aws-eks/tests.sh similarity index 64% rename from automated-tests/e2e-tests/aws-eks/tests.sh rename to test/e2e/aws-eks/tests.sh index 19b81787b..6906ddb6e 100644 --- a/automated-tests/e2e-tests/aws-eks/tests.sh +++ b/test/e2e/aws-eks/tests.sh @@ -3,7 +3,6 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. - load "./../../helper" OS="linux" @@ -30,7 +29,7 @@ CPUARCH="amd64_v1" @test "Prepare bootstrap.yml file" { info init(){ - envsubst < ./automated-tests/e2e-tests/aws-eks/bootstrap.tpl.yml > ./automated-tests/e2e-tests/aws-eks/bootstrap.yml + envsubst < ./test/e2e/aws-eks/bootstrap.tpl.yml > ./test/e2e/aws-eks/bootstrap.yml } run init [ "$status" -eq 0 ] @@ -39,7 +38,7 @@ CPUARCH="amd64_v1" @test "Bootstrap init" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./automated-tests/e2e-tests/aws-eks/bootstrap.yml -w ./automated-tests/e2e-tests/aws-eks/bootstrap --reset + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap --reset } run init @@ -52,14 +51,14 @@ CPUARCH="amd64_v1" @test "Bootstrap apply (dry-run)" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --dry-run --config ./automated-tests/e2e-tests/aws-eks/bootstrap.yml -w ./automated-tests/e2e-tests/aws-eks/bootstrap + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --dry-run --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap } run apply if [[ $status -ne 0 ]]; then echo "$output" >&3 echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/aws-eks/bootstrap/logs/terraform.logs >&3 + cat ./test/e2e/aws-eks/bootstrap/logs/terraform.logs >&3 fi [ "$status" -eq 0 ] } @@ -67,14 +66,14 @@ CPUARCH="amd64_v1" @test "Bootstrap apply" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --config ./automated-tests/e2e-tests/aws-eks/bootstrap.yml -w ./automated-tests/e2e-tests/aws-eks/bootstrap + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap } run apply if [[ $status -ne 0 ]]; then echo "$output" >&3 echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/aws-eks/bootstrap/logs/terraform.logs >&3 + cat ./test/e2e/aws-eks/bootstrap/logs/terraform.logs >&3 fi [ "$status" -eq 0 ] } @@ -82,7 +81,7 @@ CPUARCH="amd64_v1" @test "Create openvpn profile" { info apply(){ - furyagent configure openvpn-client --client-name "e2e-${CI_BUILD_NUMBER}" --config ./automated-tests/e2e-tests/aws-eks/bootstrap/secrets/furyagent.yml > /tmp/e2e.ovpn + furyagent configure openvpn-client --client-name "e2e-${CI_BUILD_NUMBER}" --config ./test/e2e/aws-eks/bootstrap/secrets/furyagent.yml > /tmp/e2e.ovpn } run apply @@ -95,7 +94,7 @@ CPUARCH="amd64_v1" @test "Wait for openvpn instance SSH port open" { info check(){ - instance_ip=$(jq -r .vpn_ip.value[0] ./automated-tests/e2e-tests/aws-eks/bootstrap/output/output.json) + instance_ip=$(jq -r .vpn_ip.value[0] ./test/e2e/aws-eks/bootstrap/output/output.json) echo " VPN Public IP: $instance_ip" >&3 wait-for -t 60 "$instance_ip:22" -- echo "VPN Instance $instance_ip SSH Port (22) UP!" } @@ -130,7 +129,7 @@ CPUARCH="amd64_v1" @test "Test Ping" { info check(){ - public_cidr=$(jq -r .public_subnets_cidr_blocks.value[0] ./automated-tests/e2e-tests/aws-eks/bootstrap/output/output.json) + public_cidr=$(jq -r .public_subnets_cidr_blocks.value[0] ./test/e2e/aws-eks/bootstrap/output/output.json) echo " Public CIDR: $public_cidr" >&3 ips=$(nmap "$public_cidr" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b") for ip in $ips; do @@ -149,13 +148,13 @@ CPUARCH="amd64_v1" @test "Prepare cluster.yml file" { info init(){ - PRIVATE_SUBNETS=$(jq -r .private_subnets.value ./automated-tests/e2e-tests/aws-eks/bootstrap/output/output.json | tr -d '\n') + PRIVATE_SUBNETS=$(jq -r .private_subnets.value ./test/e2e/aws-eks/bootstrap/output/output.json | tr -d '\n') export PRIVATE_SUBNETS - NETWORK_ID=$(jq -r .vpc_id.value ./automated-tests/e2e-tests/aws-eks/bootstrap/output/output.json) + NETWORK_ID=$(jq -r .vpc_id.value ./test/e2e/aws-eks/bootstrap/output/output.json) export NETWORK_ID - NETWORK_CIDR=$(jq -r .vpc_cidr_block.value ./automated-tests/e2e-tests/aws-eks/bootstrap/output/output.json) + NETWORK_CIDR=$(jq -r .vpc_cidr_block.value ./test/e2e/aws-eks/bootstrap/output/output.json) export NETWORK_CIDR - envsubst < ./automated-tests/e2e-tests/aws-eks/cluster.tpl.yml > ./automated-tests/e2e-tests/aws-eks/cluster.yml + envsubst < ./test/e2e/aws-eks/cluster.tpl.yml > ./test/e2e/aws-eks/cluster.yml } run init [ "$status" -eq 0 ] @@ -164,7 +163,7 @@ CPUARCH="amd64_v1" @test "Cluster init" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./automated-tests/e2e-tests/aws-eks/cluster.yml -w ./automated-tests/e2e-tests/aws-eks/cluster --reset + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster --reset } run init @@ -177,14 +176,14 @@ CPUARCH="amd64_v1" @test "Cluster apply (dry-run)" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --dry-run --config ./automated-tests/e2e-tests/aws-eks/cluster.yml -w ./automated-tests/e2e-tests/aws-eks/cluster + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --dry-run --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster } run apply if [[ $status -ne 0 ]]; then echo "$output" >&3 echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/logs/terraform.logs >&3 + cat ./test/e2e/aws-eks/cluster/logs/terraform.logs >&3 fi [ "$status" -eq 0 ] } @@ -192,14 +191,14 @@ CPUARCH="amd64_v1" @test "Cluster apply" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --config ./automated-tests/e2e-tests/aws-eks/cluster.yml -w ./automated-tests/e2e-tests/aws-eks/cluster + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster } run apply if [[ $status -ne 0 ]]; then echo "$output" >&3 echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/logs/terraform.logs >&3 + cat ./test/e2e/aws-eks/cluster/logs/terraform.logs >&3 fi [ "$status" -eq 0 ] } @@ -207,14 +206,14 @@ CPUARCH="amd64_v1" @test "kubectl get pods" { info cluster_info(){ - export KUBECONFIG=./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig + export KUBECONFIG=./test/e2e/aws-eks/cluster/secrets/kubeconfig kubectl get pods -A >&3 } run cluster_info if [[ $status -ne 0 ]]; then echo "$output" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig >&3 + cat ./test/e2e/aws-eks/cluster/secrets/kubeconfig >&3 fi [ "$status" -eq 0 ] } @@ -222,14 +221,14 @@ CPUARCH="amd64_v1" @test "kubectl get nodes" { info cluster_info(){ - export KUBECONFIG=./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig + export KUBECONFIG=./test/e2e/aws-eks/cluster/secrets/kubeconfig kubectl get nodes -o wide --show-labels >&3 } run cluster_info if [[ $status -ne 0 ]]; then echo "$output" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig >&3 + cat ./test/e2e/aws-eks/cluster/secrets/kubeconfig >&3 fi [ "$status" -eq 0 ] } @@ -237,7 +236,7 @@ CPUARCH="amd64_v1" @test "kubectl get nodes verify spot presence" { info test(){ - export KUBECONFIG=./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig + export KUBECONFIG=./test/e2e/aws-eks/cluster/secrets/kubeconfig data=$(kubectl get nodes --show-labels | grep "node.kubernetes.io/lifecycle=spot") if [ "${data}" == "" ]; then return 1; fi } @@ -245,7 +244,7 @@ CPUARCH="amd64_v1" status=${loop_it_result} if [[ $status -ne 0 ]]; then echo "$output" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/secrets/kubeconfig >&3 + cat ./test/e2e/aws-eks/cluster/secrets/kubeconfig >&3 fi [ "$status" -eq 0 ] } @@ -253,14 +252,14 @@ CPUARCH="amd64_v1" @test "Cluster destroy" { info destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster destroy --force --config ./automated-tests/e2e-tests/aws-eks/cluster.yml -w ./automated-tests/e2e-tests/aws-eks/cluster + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster destroy --force --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster } run destroy if [[ $status -ne 0 ]]; then echo "$output" >&3 echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/aws-eks/cluster/logs/terraform.logs >&3 + cat ./test/e2e/aws-eks/cluster/logs/terraform.logs >&3 fi [ "$status" -eq 0 ] } @@ -281,14 +280,14 @@ CPUARCH="amd64_v1" @test "Bootstrap destroy" { info destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap destroy --force --config ./automated-tests/e2e-tests/aws-eks/bootstrap.yml -w ./automated-tests/e2e-tests/aws-eks/bootstrap + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap destroy --force --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap } run destroy if [[ $status -ne 0 ]]; then echo "$output" >&3 echo " TERRAFORM LOGS:" >&3 - cat ./automated-tests/e2e-tests/aws-eks/bootstrap/logs/terraform.logs >&3 + cat ./test/e2e/aws-eks/bootstrap/logs/terraform.logs >&3 fi [ "$status" -eq 0 ] } diff --git a/automated-tests/helper.bash b/test/helper.bash similarity index 100% rename from automated-tests/helper.bash rename to test/helper.bash diff --git a/automated-tests/integration/aws-eks/.gitignore b/test/integration/aws-eks/.gitignore similarity index 100% rename from automated-tests/integration/aws-eks/.gitignore rename to test/integration/aws-eks/.gitignore diff --git a/automated-tests/integration/aws-eks/bootstrap.yml b/test/integration/aws-eks/bootstrap.yml similarity index 100% rename from automated-tests/integration/aws-eks/bootstrap.yml rename to test/integration/aws-eks/bootstrap.yml diff --git a/automated-tests/integration/aws-eks/cluster.yml b/test/integration/aws-eks/cluster.yml similarity index 100% rename from automated-tests/integration/aws-eks/cluster.yml rename to test/integration/aws-eks/cluster.yml diff --git a/automated-tests/integration/aws-eks/tests.sh b/test/integration/aws-eks/tests.sh similarity index 84% rename from automated-tests/integration/aws-eks/tests.sh rename to test/integration/aws-eks/tests.sh index d21feeeb6..9ae497c3c 100644 --- a/automated-tests/integration/aws-eks/tests.sh +++ b/test/integration/aws-eks/tests.sh @@ -30,7 +30,7 @@ CPUARCH="amd64_v1" @test "Bootstrap init" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./automated-tests/integration/aws-eks/bootstrap.yml -w ./automated-tests/integration/aws-eks/bootstrap --reset + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./test/integration/aws-eks/bootstrap.yml -w ./test/integration/aws-eks/bootstrap --reset } run init @@ -42,7 +42,7 @@ CPUARCH="amd64_v1" @test "Bootstrap structure" { info - project_dir="./automated-tests/integration/aws-eks/bootstrap" + project_dir="./test/integration/aws-eks/bootstrap" test(){ if [ -e ${project_dir}/bin/terraform ] && [ -e ${project_dir}/configuration/.netrc ] && [ -e ${project_dir}/logs/terraform.logs ] && [ -e ${project_dir}/.gitignore ] && [ -e ${project_dir}/.gitattributes ] && [ -e ${project_dir}/.gitattributes ] then @@ -63,7 +63,7 @@ CPUARCH="amd64_v1" @test "Cluster init" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./automated-tests/integration/aws-eks/cluster.yml -w ./automated-tests/integration/aws-eks/cluster --reset + ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./test/integration/aws-eks/cluster.yml -w ./test/integration/aws-eks/cluster --reset } run init if [[ ${status} -ne 0 ]]; then @@ -74,7 +74,7 @@ CPUARCH="amd64_v1" @test "Cluster structure" { info - project_dir="./automated-tests/integration/aws-eks/cluster" + project_dir="./test/integration/aws-eks/cluster" test(){ if [ -e ${project_dir}/bin/terraform ] && [ -e ${project_dir}/configuration/.netrc ] && [ -e ${project_dir}/logs/terraform.logs ] && [ -e ${project_dir}/.gitignore ] && [ -e ${project_dir}/.gitattributes ] && [ -e ${project_dir}/backend.tf ] then diff --git a/automated-tests/integration/template-engine/.gitignore b/test/integration/template-engine/.gitignore similarity index 100% rename from automated-tests/integration/template-engine/.gitignore rename to test/integration/template-engine/.gitignore diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml b/test/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml rename to test/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/distribution.yaml b/test/integration/template-engine/test-data/complex-dry-run/distribution.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex-dry-run/distribution.yaml rename to test/integration/template-engine/test-data/complex-dry-run/distribution.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/furyctl.yaml b/test/integration/template-engine/test-data/complex-dry-run/furyctl.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex-dry-run/furyctl.yaml rename to test/integration/template-engine/test-data/complex-dry-run/furyctl.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml b/test/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml rename to test/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl b/test/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl rename to test/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl diff --git a/automated-tests/integration/template-engine/test-data/complex/data/expected-kustomization.yaml b/test/integration/template-engine/test-data/complex/data/expected-kustomization.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex/data/expected-kustomization.yaml rename to test/integration/template-engine/test-data/complex/data/expected-kustomization.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex/distribution.yaml b/test/integration/template-engine/test-data/complex/distribution.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex/distribution.yaml rename to test/integration/template-engine/test-data/complex/distribution.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex/furyctl.yaml b/test/integration/template-engine/test-data/complex/furyctl.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex/furyctl.yaml rename to test/integration/template-engine/test-data/complex/furyctl.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex-dry-run/target/config/example.yaml b/test/integration/template-engine/test-data/complex/source/config/example.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex-dry-run/target/config/example.yaml rename to test/integration/template-engine/test-data/complex/source/config/example.yaml diff --git a/automated-tests/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl b/test/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl similarity index 100% rename from automated-tests/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl rename to test/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml b/test/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml rename to test/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml b/test/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml rename to test/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl b/test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl similarity index 100% rename from automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl rename to test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt b/test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt similarity index 100% rename from automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt rename to test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt diff --git a/automated-tests/integration/template-engine/test-data/empty/source/file.txt.tpl b/test/integration/template-engine/test-data/empty/source/file.txt.tpl similarity index 100% rename from automated-tests/integration/template-engine/test-data/empty/source/file.txt.tpl rename to test/integration/template-engine/test-data/empty/source/file.txt.tpl diff --git a/automated-tests/integration/template-engine/test-data/no-distribution-yaml/.gitkeep b/test/integration/template-engine/test-data/no-distribution-yaml/.gitkeep similarity index 100% rename from automated-tests/integration/template-engine/test-data/no-distribution-yaml/.gitkeep rename to test/integration/template-engine/test-data/no-distribution-yaml/.gitkeep diff --git a/automated-tests/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml b/test/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml rename to test/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/distribution.yaml b/test/integration/template-engine/test-data/simple-dry-run/distribution.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple-dry-run/distribution.yaml rename to test/integration/template-engine/test-data/simple-dry-run/distribution.yaml diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/furyctl.yaml b/test/integration/template-engine/test-data/simple-dry-run/furyctl.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple-dry-run/furyctl.yaml rename to test/integration/template-engine/test-data/simple-dry-run/furyctl.yaml diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl b/test/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl rename to test/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl diff --git a/automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target/keepfile.txt b/test/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt similarity index 100% rename from automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property/target/keepfile.txt rename to test/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt diff --git a/automated-tests/integration/template-engine/test-data/simple/distribution.yaml b/test/integration/template-engine/test-data/simple/distribution.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple/distribution.yaml rename to test/integration/template-engine/test-data/simple/distribution.yaml diff --git a/automated-tests/integration/template-engine/test-data/simple/furyctl.yaml b/test/integration/template-engine/test-data/simple/furyctl.yaml similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple/furyctl.yaml rename to test/integration/template-engine/test-data/simple/furyctl.yaml diff --git a/automated-tests/integration/template-engine/test-data/simple/source/file.txt.tpl b/test/integration/template-engine/test-data/simple/source/file.txt.tpl similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple/source/file.txt.tpl rename to test/integration/template-engine/test-data/simple/source/file.txt.tpl diff --git a/automated-tests/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt b/test/integration/template-engine/test-data/simple/source/keepfile.txt similarity index 100% rename from automated-tests/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt rename to test/integration/template-engine/test-data/simple/source/keepfile.txt diff --git a/automated-tests/integration/template-engine/tests.sh b/test/integration/template-engine/tests.sh similarity index 89% rename from automated-tests/integration/template-engine/tests.sh rename to test/integration/template-engine/tests.sh index 1a3b0f010..68127dab1 100644 --- a/automated-tests/integration/template-engine/tests.sh +++ b/test/integration/template-engine/tests.sh @@ -29,7 +29,7 @@ fi @test "no distribution file" { info - test_dir="./automated-tests/integration/template-engine/test-data/no-distribution-yaml" + test_dir="./test/integration/template-engine/test-data/no-distribution-yaml" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template @@ -46,7 +46,7 @@ fi @test "no furyctl.yaml file" { info - test_dir="./automated-tests/integration/template-engine/test-data/no-furyctl-yaml" + test_dir="./test/integration/template-engine/test-data/no-furyctl-yaml" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template @@ -63,7 +63,7 @@ fi @test "no data property in distribution.yaml file" { info - test_dir="./automated-tests/integration/template-engine/test-data/distribution-yaml-no-data-property" + test_dir="./test/integration/template-engine/test-data/distribution-yaml-no-data-property" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template @@ -80,7 +80,7 @@ fi @test "empty template" { info - test_dir="./automated-tests/integration/template-engine/test-data/empty" + test_dir="./test/integration/template-engine/test-data/empty" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template @@ -96,7 +96,7 @@ fi @test "simple template dry-run" { info - test_dir="./automated-tests/integration/template-engine/test-data/simple-dry-run" + test_dir="./test/integration/template-engine/test-data/simple-dry-run" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template --dry-run @@ -112,7 +112,7 @@ fi @test "simple template" { info - test_dir="./automated-tests/integration/template-engine/test-data/simple" + test_dir="./test/integration/template-engine/test-data/simple" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template @@ -128,7 +128,7 @@ fi @test "complex template dry-run" { info - test_dir="./automated-tests/integration/template-engine/test-data/complex" + test_dir="./test/integration/template-engine/test-data/complex" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template --dry-run @@ -168,7 +168,7 @@ fi @test "complex template" { info - test_dir="./automated-tests/integration/template-engine/test-data/complex" + test_dir="./test/integration/template-engine/test-data/complex" furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && ${furyctl_bin} -d --debug template diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml b/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml rename to test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml b/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml rename to test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml b/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml rename to test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json rename to test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml b/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml rename to test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml b/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml rename to test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml b/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml rename to test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json rename to test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/ansible b/test/integration/validation-cmd/test-data/dependencies-correct/ansible similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/ansible rename to test/integration/validation-cmd/test-data/dependencies-correct/ansible diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/furyagent b/test/integration/validation-cmd/test-data/dependencies-correct/furyagent similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/furyagent rename to test/integration/validation-cmd/test-data/dependencies-correct/furyagent diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml b/test/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml rename to test/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml b/test/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml rename to test/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/kubectl b/test/integration/validation-cmd/test-data/dependencies-correct/kubectl similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/kubectl rename to test/integration/validation-cmd/test-data/dependencies-correct/kubectl diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/kustomize b/test/integration/validation-cmd/test-data/dependencies-correct/kustomize similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/kustomize rename to test/integration/validation-cmd/test-data/dependencies-correct/kustomize diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-correct/terraform b/test/integration/validation-cmd/test-data/dependencies-correct/terraform similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-correct/terraform rename to test/integration/validation-cmd/test-data/dependencies-correct/terraform diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml b/test/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml rename to test/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml b/test/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml rename to test/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/ansible b/test/integration/validation-cmd/test-data/dependencies-wrong/ansible similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/ansible rename to test/integration/validation-cmd/test-data/dependencies-wrong/ansible diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/furyagent b/test/integration/validation-cmd/test-data/dependencies-wrong/furyagent similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/furyagent rename to test/integration/validation-cmd/test-data/dependencies-wrong/furyagent diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml b/test/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml rename to test/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml b/test/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml rename to test/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kubectl b/test/integration/validation-cmd/test-data/dependencies-wrong/kubectl similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kubectl rename to test/integration/validation-cmd/test-data/dependencies-wrong/kubectl diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kustomize b/test/integration/validation-cmd/test-data/dependencies-wrong/kustomize similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/kustomize rename to test/integration/validation-cmd/test-data/dependencies-wrong/kustomize diff --git a/automated-tests/integration/validation-cmd/test-data/dependencies-wrong/terraform b/test/integration/validation-cmd/test-data/dependencies-wrong/terraform similarity index 100% rename from automated-tests/integration/validation-cmd/test-data/dependencies-wrong/terraform rename to test/integration/validation-cmd/test-data/dependencies-wrong/terraform diff --git a/automated-tests/integration/validation-cmd/tests.sh b/test/integration/validation-cmd/tests.sh similarity index 90% rename from automated-tests/integration/validation-cmd/tests.sh rename to test/integration/validation-cmd/tests.sh index c76960104..f25424e6c 100644 --- a/automated-tests/integration/validation-cmd/tests.sh +++ b/test/integration/validation-cmd/tests.sh @@ -29,7 +29,7 @@ CPUARCH="amd64_v1" @test "invalid furyctl yaml" { info - test_dir="./automated-tests/integration/validation-cmd/test-data/config-invalid-furyctl-yaml" + test_dir="./test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml" abs_test_dir=${PWD}/${test_dir} furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ @@ -47,7 +47,7 @@ CPUARCH="amd64_v1" @test "valid furyctl yaml" { info - test_dir="./automated-tests/integration/validation-cmd/test-data/config-valid-furyctl-yaml" + test_dir="./test/integration/validation-cmd/test-data/config-valid-furyctl-yaml" abs_test_dir=${PWD}/${test_dir} furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ @@ -60,7 +60,7 @@ CPUARCH="amd64_v1" @test "dependencies missing" { info - test_dir="./automated-tests/integration/validation-cmd/test-data/dependencies-missing" + test_dir="./test/integration/validation-cmd/test-data/dependencies-missing" abs_test_dir=${PWD}/${test_dir} furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ @@ -88,7 +88,7 @@ CPUARCH="amd64_v1" @test "wrong dependencies installed" { info - test_dir="./automated-tests/integration/validation-cmd/test-data/dependencies-wrong" + test_dir="./test/integration/validation-cmd/test-data/dependencies-wrong" abs_test_dir=${PWD}/${test_dir} furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ @@ -116,7 +116,7 @@ CPUARCH="amd64_v1" @test "correct dependencies installed" { info - test_dir="./automated-tests/integration/validation-cmd/test-data/dependencies-correct" + test_dir="./test/integration/validation-cmd/test-data/dependencies-correct" abs_test_dir=${PWD}/${test_dir} furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ From 1c3899ab09ba7081d511849f2fd924e9df43e5e4 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:13:34 +0200 Subject: [PATCH 028/383] chore: update goreleaser version --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1abdbe212..67d0214a0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -32,7 +32,7 @@ steps: CGO_ENABLED: 0 - name: build - image: ghcr.io/goreleaser/goreleaser:v1.9.2 + image: ghcr.io/goreleaser/goreleaser:v1.11.4 pull: always depends_on: - lint @@ -97,7 +97,7 @@ steps: - tag - name: build-release - image: ghcr.io/goreleaser/goreleaser:v1.9.2 + image: ghcr.io/goreleaser/goreleaser:v1.11.4 pull: always depends_on: - lint From e60cfbf5f087722cea34f2e037ee11a668aee7d2 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:24:21 +0200 Subject: [PATCH 029/383] chore: remove integration tests, refactor furyctl bin path in tests --- .drone.yml | 18 ++--- test/e2e/aws-eks/tests.sh | 20 ++--- test/integration/aws-eks/.gitignore | 2 - test/integration/aws-eks/bootstrap.yml | 18 ----- test/integration/aws-eks/cluster.yml | 29 ------- test/integration/aws-eks/tests.sh | 93 ----------------------- test/integration/template-engine/tests.sh | 34 ++++----- test/integration/validation-cmd/tests.sh | 19 ++--- 8 files changed, 42 insertions(+), 191 deletions(-) delete mode 100644 test/integration/aws-eks/.gitignore delete mode 100644 test/integration/aws-eks/bootstrap.yml delete mode 100644 test/integration/aws-eks/cluster.yml delete mode 100644 test/integration/aws-eks/tests.sh diff --git a/.drone.yml b/.drone.yml index 67d0214a0..6a4ae23ba 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,8 +47,7 @@ steps: - git fetch --tags - make build - - &integration - name: integration-aws-eks + - name: integration-template-engine image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 depends_on: - build @@ -56,16 +55,17 @@ steps: CGO_ENABLED: 0 FURYCTL_TOKEN: from_secret: FURYCTL_TOKEN - commands: - - bats -t ./test/integration/aws-eks/tests.sh - - - <<: *integration - name: integration-template-engine commands: - bats -t ./test/integration/template-engine/tests.sh - - <<: *integration - name: integration-validation-cmd + - name: integration-validation-cmd + image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 + depends_on: + - build + environment: + CGO_ENABLED: 0 + FURYCTL_TOKEN: + from_secret: FURYCTL_TOKEN commands: - bats -t ./test/integration/validation-cmd/tests.sh diff --git a/test/e2e/aws-eks/tests.sh b/test/e2e/aws-eks/tests.sh index 6906ddb6e..168c26b24 100644 --- a/test/e2e/aws-eks/tests.sh +++ b/test/e2e/aws-eks/tests.sh @@ -14,10 +14,12 @@ CPUARCH="amd64_v1" # CPUARCH="arm64" # fi +FURYCTL=./dist/furyctl_${OS}_${CPUARCH}/furyctl + @test "furyctl" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty version + ${FURYCTL} --no-tty version } run init if [[ $status -ne 0 ]]; then @@ -38,7 +40,7 @@ CPUARCH="amd64_v1" @test "Bootstrap init" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap --reset + ${FURYCTL} --no-tty -d --debug bootstrap init --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap --reset } run init @@ -51,7 +53,7 @@ CPUARCH="amd64_v1" @test "Bootstrap apply (dry-run)" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --dry-run --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap + ${FURYCTL} --no-tty -d --debug bootstrap apply --dry-run --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap } run apply @@ -66,7 +68,7 @@ CPUARCH="amd64_v1" @test "Bootstrap apply" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap apply --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap + ${FURYCTL} --no-tty -d --debug bootstrap apply --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap } run apply @@ -163,7 +165,7 @@ CPUARCH="amd64_v1" @test "Cluster init" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster --reset + ${FURYCTL} --no-tty -d --debug cluster init --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster --reset } run init @@ -176,7 +178,7 @@ CPUARCH="amd64_v1" @test "Cluster apply (dry-run)" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --dry-run --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster + ${FURYCTL} --no-tty -d --debug cluster apply --dry-run --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster } run apply @@ -191,7 +193,7 @@ CPUARCH="amd64_v1" @test "Cluster apply" { info apply(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster apply --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster + ${FURYCTL} --no-tty -d --debug cluster apply --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster } run apply @@ -252,7 +254,7 @@ CPUARCH="amd64_v1" @test "Cluster destroy" { info destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster destroy --force --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster + ${FURYCTL} --no-tty -d --debug cluster destroy --force --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster } run destroy @@ -280,7 +282,7 @@ CPUARCH="amd64_v1" @test "Bootstrap destroy" { info destroy(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap destroy --force --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap + ${FURYCTL} --no-tty -d --debug bootstrap destroy --force --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap } run destroy diff --git a/test/integration/aws-eks/.gitignore b/test/integration/aws-eks/.gitignore deleted file mode 100644 index dd6c13d71..000000000 --- a/test/integration/aws-eks/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -cluster -bootstrap \ No newline at end of file diff --git a/test/integration/aws-eks/bootstrap.yml b/test/integration/aws-eks/bootstrap.yml deleted file mode 100644 index 14fd0f495..000000000 --- a/test/integration/aws-eks/bootstrap.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: furyctl -provisioner: aws -spec: - networkCIDR: 10.0.0.0/16 - publicSubnetsCIDRs: - - 10.0.1.0/24 - privateSubnetsCIDRs: - - 10.0.101.0/24 - vpn: - subnetCIDR: 192.168.200.0/24 - sshUsers: - - angelbarrera92 diff --git a/test/integration/aws-eks/cluster.yml b/test/integration/aws-eks/cluster.yml deleted file mode 100644 index 765c03283..000000000 --- a/test/integration/aws-eks/cluster.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: furyctl -provisioner: eks -spec: - version: "1.20" - network: vpc-tobedefined - subnetworks: - - subnet-tobedefined-1 - dmzCIDRRange: 10.0.0.0/16 - sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCefFo9ASM8grncpLpJr+DAeGzTtoIaxnqSqrPeSWlCyManFz5M/DDkbnql8PdrENFU28blZyIxu93d5U0RhXZumXk1utpe0L/9UtImnOGG6/dKv9fV9vcJH45XdD3rCV21ZMG1nuhxlN0DftcuUubt/VcHXflBGaLrs18DrMuHVIbyb5WO4wQ9Od/SoJZyR6CZmIEqag6ADx4aFcdsUwK1Cpc51LhPbkdXGGjipiwP45q0I6/Brjxv/Kia1e+RmIRHiltsVBdKKTL9hqu9esbAod9I5BkBtbB5bmhQUVFZehi+d/opPvsIszE/coW5r/g/EVf9zZswebFPcsNr85+x - nodePoolsLaunchKind: "launch_templates" - nodePools: - - name: my-node-pool - minSize: 1 - maxSize: 1 - volumeSize: 50 - instanceType: t3.micro - - name: my-spot-node-pool - minSize: 1 - maxSize: 1 - spotInstance: true - volumeSize: 50 - instanceType: t3.micro - diff --git a/test/integration/aws-eks/tests.sh b/test/integration/aws-eks/tests.sh deleted file mode 100644 index 9ae497c3c..000000000 --- a/test/integration/aws-eks/tests.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - -load "./../../helper" - -OS="linux" -if [[ "${OSTYPE}" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -@test "furyctl" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty version - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Bootstrap init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug bootstrap init --config ./test/integration/aws-eks/bootstrap.yml -w ./test/integration/aws-eks/bootstrap --reset - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Bootstrap structure" { - info - project_dir="./test/integration/aws-eks/bootstrap" - test(){ - if [ -e ${project_dir}/bin/terraform ] && [ -e ${project_dir}/configuration/.netrc ] && [ -e ${project_dir}/logs/terraform.logs ] && [ -e ${project_dir}/.gitignore ] && [ -e ${project_dir}/.gitattributes ] && [ -e ${project_dir}/.gitattributes ] - then - echo " All files exist, directory intact" >&3 - return 0 - else - echo " One or more files are missing" >&3 - return 1 - fi - } - run test - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Cluster init" { - info - init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl --no-tty -d --debug cluster init --config ./test/integration/aws-eks/cluster.yml -w ./test/integration/aws-eks/cluster --reset - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "Cluster structure" { - info - project_dir="./test/integration/aws-eks/cluster" - test(){ - if [ -e ${project_dir}/bin/terraform ] && [ -e ${project_dir}/configuration/.netrc ] && [ -e ${project_dir}/logs/terraform.logs ] && [ -e ${project_dir}/.gitignore ] && [ -e ${project_dir}/.gitattributes ] && [ -e ${project_dir}/backend.tf ] - then - echo " All files exist, directory intact" >&3 - return 0 - else - echo " One or more files are missing" >&3 - return 1 - fi - } - run test - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} diff --git a/test/integration/template-engine/tests.sh b/test/integration/template-engine/tests.sh index 68127dab1..0ed69043c 100644 --- a/test/integration/template-engine/tests.sh +++ b/test/integration/template-engine/tests.sh @@ -11,14 +11,16 @@ if [[ "${OSTYPE}" == "darwin"* ]]; then OS="darwin" fi CPUARCH="amd64_v1" -if [ "$(uname -m)" = "arm64" ]; then - CPUARCH="arm64" -fi +# if [ "$(uname -m)" = "arm64" ]; then +# CPUARCH="arm64" +# fi + +FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "furyctl" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl version + "${FURYCTL}" version } run init if [[ ${status} -ne 0 ]]; then @@ -30,9 +32,8 @@ fi @test "no distribution file" { info test_dir="./test/integration/template-engine/test-data/no-distribution-yaml" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug template } run init @@ -47,9 +48,8 @@ fi @test "no furyctl.yaml file" { info test_dir="./test/integration/template-engine/test-data/no-furyctl-yaml" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug template } run init @@ -64,9 +64,8 @@ fi @test "no data property in distribution.yaml file" { info test_dir="./test/integration/template-engine/test-data/distribution-yaml-no-data-property" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug template } run init @@ -81,9 +80,8 @@ fi @test "empty template" { info test_dir="./test/integration/template-engine/test-data/empty" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug template if [ -f ./target/file.txt ]; then false; else true; fi } run init @@ -97,9 +95,8 @@ fi @test "simple template dry-run" { info test_dir="./test/integration/template-engine/test-data/simple-dry-run" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template --dry-run + cd ${test_dir} && ${FURYCTL} -d --debug template --dry-run cat ./target/file.txt | grep "testValue" } run init @@ -113,9 +110,8 @@ fi @test "simple template" { info test_dir="./test/integration/template-engine/test-data/simple" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug template cat ./target/file.txt | grep "testValue" } run init @@ -129,9 +125,8 @@ fi @test "complex template dry-run" { info test_dir="./test/integration/template-engine/test-data/complex" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template --dry-run + cd ${test_dir} && ${FURYCTL} -d --debug template --dry-run # test that the config/example.yaml file has been generated if [ ! -f ./target/config/example.yaml ]; then @@ -169,9 +164,8 @@ fi @test "complex template" { info test_dir="./test/integration/template-engine/test-data/complex" - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug template # test that the config/example.yaml file has been generated if [ ! -f ./target/config/example.yaml ]; then diff --git a/test/integration/validation-cmd/tests.sh b/test/integration/validation-cmd/tests.sh index f25424e6c..4e30e17a0 100644 --- a/test/integration/validation-cmd/tests.sh +++ b/test/integration/validation-cmd/tests.sh @@ -15,10 +15,12 @@ CPUARCH="amd64_v1" # CPUARCH="arm64" # fi +FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" + @test "furyctl" { info init(){ - ./dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl version + "${FURYCTL} version" } run init if [[ ${status} -ne 0 ]]; then @@ -31,9 +33,8 @@ CPUARCH="amd64_v1" info test_dir="./test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml" abs_test_dir=${PWD}/${test_dir} - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} + cd ${test_dir} && ${FURYCTL} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} } run init @@ -49,9 +50,8 @@ CPUARCH="amd64_v1" info test_dir="./test/integration/validation-cmd/test-data/config-valid-furyctl-yaml" abs_test_dir=${PWD}/${test_dir} - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ - cd ${test_dir} && ${furyctl_bin} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} + cd ${test_dir} && ${FURYCTL} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} } run init @@ -62,10 +62,9 @@ CPUARCH="amd64_v1" info test_dir="./test/integration/validation-cmd/test-data/dependencies-missing" abs_test_dir=${PWD}/${test_dir} - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && \ - ${furyctl_bin} -d --debug \ + ${FURYCTL} -d --debug \ validate dependencies \ --config ${abs_test_dir}/furyctl.yaml \ --distro-location ${abs_test_dir} \ @@ -90,10 +89,9 @@ CPUARCH="amd64_v1" info test_dir="./test/integration/validation-cmd/test-data/dependencies-wrong" abs_test_dir=${PWD}/${test_dir} - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ cd ${test_dir} && \ - ${furyctl_bin} -d --debug \ + ${FURYCTL} -d --debug \ validate dependencies \ --config ${abs_test_dir}/furyctl.yaml \ --distro-location ${abs_test_dir} \ @@ -118,14 +116,13 @@ CPUARCH="amd64_v1" info test_dir="./test/integration/validation-cmd/test-data/dependencies-correct" abs_test_dir=${PWD}/${test_dir} - furyctl_bin=${PWD}/dist/furyctl-${OS}_${OS}_${CPUARCH}/furyctl init(){ export AWS_ACCESS_KEY_ID=foo export AWS_SECRET_ACCESS_KEY=bar export AWS_DEFAULT_REGION=baz cd ${test_dir} && \ - ${furyctl_bin} -d --debug \ + ${FURYCTL} -d --debug \ validate dependencies \ --config ${abs_test_dir}/furyctl.yaml \ --distro-location ${abs_test_dir} \ From 3adf04b376a7d800dfb16e97d4460e11aabb91da Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:28:09 +0200 Subject: [PATCH 030/383] fix: pipeline deps --- .drone.yml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/.drone.yml b/.drone.yml index 6a4ae23ba..b2a4b343d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,7 +47,7 @@ steps: - git fetch --tags - make build - - name: integration-template-engine + - name: test-integration image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 depends_on: - build @@ -57,19 +57,9 @@ steps: from_secret: FURYCTL_TOKEN commands: - bats -t ./test/integration/template-engine/tests.sh - - - name: integration-validation-cmd - image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 - depends_on: - - build - environment: - CGO_ENABLED: 0 - FURYCTL_TOKEN: - from_secret: FURYCTL_TOKEN - commands: - bats -t ./test/integration/validation-cmd/tests.sh - - name: e2e-aws + - name: test-e2e image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 pull: always privileged: true # Required to connect to the VPN @@ -79,7 +69,6 @@ steps: CGO_ENABLED: 0 FURYCTL_TOKEN: from_secret: FURYCTL_TOKEN - TERRAFORM_TF_STATES_BUCKET_NAME: from_secret: TERRAFORM_TF_STATES_BUCKET_NAME AWS_ACCESS_KEY_ID: @@ -103,10 +92,9 @@ steps: - lint - test - build - - integration-aws-eks - - integration-template-engine + - test-integration - integration-validation-cmd - - e2e-aws + - test-e2e environment: CGO_ENABLED: 0 GITHUB_TOKEN: From 7ed4a24acfc9d7d5695cf642474a7e9f4146cb9e Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:28:38 +0200 Subject: [PATCH 031/383] fix: more pipeline deps --- .drone.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index b2a4b343d..95fecc87d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -93,7 +93,6 @@ steps: - test - build - test-integration - - integration-validation-cmd - test-e2e environment: CGO_ENABLED: 0 From 5dc4a4df4921b909880c00d0856a3685581da4e9 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:54:39 +0200 Subject: [PATCH 032/383] chore: rename test folder to make it comply with go standard layout --- .../data/expected-kustomization.yaml | 0 .../complex-dry-run/distribution.yaml | 0 .../complex-dry-run/furyctl.yaml | 0 .../source/config/example.yaml | 0 .../source/kustomization.yaml.tpl | 0 .../complex/data/expected-kustomization.yaml | 0 .../complex/distribution.yaml | 0 .../{test-data => data}/complex/furyctl.yaml | 0 .../complex/source/config/example.yaml | 0 .../complex/source/kustomization.yaml.tpl | 0 .../distribution.yaml | 0 .../furyctl.yaml | 0 .../source/file.txt.tpl | 0 .../source/keepfile.txt | 0 .../empty/source/file.txt.tpl | 0 .../no-distribution-yaml/.gitkeep | 0 .../no-furyctl-yaml/distribution.yaml | 0 .../simple-dry-run/distribution.yaml | 0 .../simple-dry-run/furyctl.yaml | 0 .../simple-dry-run/source/file.txt.tpl | 0 .../simple-dry-run/source/keepfile.txt | 0 .../simple/distribution.yaml | 0 .../{test-data => data}/simple/furyctl.yaml | 0 .../simple/source/file.txt.tpl | 0 .../simple/source/keepfile.txt | 0 test/integration/template-engine/tests.sh | 32 +++++++++---------- .../furyctl-defaults.yaml | 0 .../config-invalid-furyctl-yaml/furyctl.yaml | 0 .../config-invalid-furyctl-yaml/kfd.yaml | 0 .../schemas/ekscluster-kfd-v1alpha2.json | 0 .../furyctl-defaults.yaml | 0 .../config-valid-furyctl-yaml/furyctl.yaml | 0 .../config-valid-furyctl-yaml/kfd.yaml | 0 .../schemas/ekscluster-kfd-v1alpha2.json | 0 .../dependencies-correct/ansible | 0 .../dependencies-correct/furyagent | 0 .../dependencies-correct/furyctl.yaml | 0 .../dependencies-correct/kfd.yaml | 0 .../dependencies-correct/kubectl | 0 .../dependencies-correct/kustomize | 0 .../dependencies-correct/terraform | 0 .../dependencies-missing/furyctl.yaml | 0 .../dependencies-missing/kfd.yaml | 0 .../dependencies-wrong/ansible | 0 .../dependencies-wrong/furyagent | 0 .../dependencies-wrong/furyctl.yaml | 0 .../dependencies-wrong/kfd.yaml | 0 .../dependencies-wrong/kubectl | 0 .../dependencies-wrong/kustomize | 0 .../dependencies-wrong/terraform | 0 test/integration/validation-cmd/tests.sh | 12 +++---- 51 files changed, 22 insertions(+), 22 deletions(-) rename test/integration/template-engine/{test-data => data}/complex-dry-run/data/expected-kustomization.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex-dry-run/distribution.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex-dry-run/furyctl.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex-dry-run/source/config/example.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex-dry-run/source/kustomization.yaml.tpl (100%) rename test/integration/template-engine/{test-data => data}/complex/data/expected-kustomization.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex/distribution.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex/furyctl.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex/source/config/example.yaml (100%) rename test/integration/template-engine/{test-data => data}/complex/source/kustomization.yaml.tpl (100%) rename test/integration/template-engine/{test-data => data}/distribution-yaml-no-data-property/distribution.yaml (100%) rename test/integration/template-engine/{test-data => data}/distribution-yaml-no-data-property/furyctl.yaml (100%) rename test/integration/template-engine/{test-data => data}/distribution-yaml-no-data-property/source/file.txt.tpl (100%) rename test/integration/template-engine/{test-data => data}/distribution-yaml-no-data-property/source/keepfile.txt (100%) rename test/integration/template-engine/{test-data => data}/empty/source/file.txt.tpl (100%) rename test/integration/template-engine/{test-data => data}/no-distribution-yaml/.gitkeep (100%) rename test/integration/template-engine/{test-data => data}/no-furyctl-yaml/distribution.yaml (100%) rename test/integration/template-engine/{test-data => data}/simple-dry-run/distribution.yaml (100%) rename test/integration/template-engine/{test-data => data}/simple-dry-run/furyctl.yaml (100%) rename test/integration/template-engine/{test-data => data}/simple-dry-run/source/file.txt.tpl (100%) rename test/integration/template-engine/{test-data => data}/simple-dry-run/source/keepfile.txt (100%) rename test/integration/template-engine/{test-data => data}/simple/distribution.yaml (100%) rename test/integration/template-engine/{test-data => data}/simple/furyctl.yaml (100%) rename test/integration/template-engine/{test-data => data}/simple/source/file.txt.tpl (100%) rename test/integration/template-engine/{test-data => data}/simple/source/keepfile.txt (100%) rename test/integration/validation-cmd/{test-data => data}/config-invalid-furyctl-yaml/furyctl-defaults.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/config-invalid-furyctl-yaml/furyctl.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/config-invalid-furyctl-yaml/kfd.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json (100%) rename test/integration/validation-cmd/{test-data => data}/config-valid-furyctl-yaml/furyctl-defaults.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/config-valid-furyctl-yaml/furyctl.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/config-valid-furyctl-yaml/kfd.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/ansible (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/furyagent (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/furyctl.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/kfd.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/kubectl (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/kustomize (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-correct/terraform (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-missing/furyctl.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-missing/kfd.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/ansible (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/furyagent (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/furyctl.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/kfd.yaml (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/kubectl (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/kustomize (100%) rename test/integration/validation-cmd/{test-data => data}/dependencies-wrong/terraform (100%) diff --git a/test/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml b/test/integration/template-engine/data/complex-dry-run/data/expected-kustomization.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex-dry-run/data/expected-kustomization.yaml rename to test/integration/template-engine/data/complex-dry-run/data/expected-kustomization.yaml diff --git a/test/integration/template-engine/test-data/complex-dry-run/distribution.yaml b/test/integration/template-engine/data/complex-dry-run/distribution.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex-dry-run/distribution.yaml rename to test/integration/template-engine/data/complex-dry-run/distribution.yaml diff --git a/test/integration/template-engine/test-data/complex-dry-run/furyctl.yaml b/test/integration/template-engine/data/complex-dry-run/furyctl.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex-dry-run/furyctl.yaml rename to test/integration/template-engine/data/complex-dry-run/furyctl.yaml diff --git a/test/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml b/test/integration/template-engine/data/complex-dry-run/source/config/example.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex-dry-run/source/config/example.yaml rename to test/integration/template-engine/data/complex-dry-run/source/config/example.yaml diff --git a/test/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl b/test/integration/template-engine/data/complex-dry-run/source/kustomization.yaml.tpl similarity index 100% rename from test/integration/template-engine/test-data/complex-dry-run/source/kustomization.yaml.tpl rename to test/integration/template-engine/data/complex-dry-run/source/kustomization.yaml.tpl diff --git a/test/integration/template-engine/test-data/complex/data/expected-kustomization.yaml b/test/integration/template-engine/data/complex/data/expected-kustomization.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex/data/expected-kustomization.yaml rename to test/integration/template-engine/data/complex/data/expected-kustomization.yaml diff --git a/test/integration/template-engine/test-data/complex/distribution.yaml b/test/integration/template-engine/data/complex/distribution.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex/distribution.yaml rename to test/integration/template-engine/data/complex/distribution.yaml diff --git a/test/integration/template-engine/test-data/complex/furyctl.yaml b/test/integration/template-engine/data/complex/furyctl.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex/furyctl.yaml rename to test/integration/template-engine/data/complex/furyctl.yaml diff --git a/test/integration/template-engine/test-data/complex/source/config/example.yaml b/test/integration/template-engine/data/complex/source/config/example.yaml similarity index 100% rename from test/integration/template-engine/test-data/complex/source/config/example.yaml rename to test/integration/template-engine/data/complex/source/config/example.yaml diff --git a/test/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl b/test/integration/template-engine/data/complex/source/kustomization.yaml.tpl similarity index 100% rename from test/integration/template-engine/test-data/complex/source/kustomization.yaml.tpl rename to test/integration/template-engine/data/complex/source/kustomization.yaml.tpl diff --git a/test/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml b/test/integration/template-engine/data/distribution-yaml-no-data-property/distribution.yaml similarity index 100% rename from test/integration/template-engine/test-data/distribution-yaml-no-data-property/distribution.yaml rename to test/integration/template-engine/data/distribution-yaml-no-data-property/distribution.yaml diff --git a/test/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml b/test/integration/template-engine/data/distribution-yaml-no-data-property/furyctl.yaml similarity index 100% rename from test/integration/template-engine/test-data/distribution-yaml-no-data-property/furyctl.yaml rename to test/integration/template-engine/data/distribution-yaml-no-data-property/furyctl.yaml diff --git a/test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl b/test/integration/template-engine/data/distribution-yaml-no-data-property/source/file.txt.tpl similarity index 100% rename from test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/file.txt.tpl rename to test/integration/template-engine/data/distribution-yaml-no-data-property/source/file.txt.tpl diff --git a/test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt b/test/integration/template-engine/data/distribution-yaml-no-data-property/source/keepfile.txt similarity index 100% rename from test/integration/template-engine/test-data/distribution-yaml-no-data-property/source/keepfile.txt rename to test/integration/template-engine/data/distribution-yaml-no-data-property/source/keepfile.txt diff --git a/test/integration/template-engine/test-data/empty/source/file.txt.tpl b/test/integration/template-engine/data/empty/source/file.txt.tpl similarity index 100% rename from test/integration/template-engine/test-data/empty/source/file.txt.tpl rename to test/integration/template-engine/data/empty/source/file.txt.tpl diff --git a/test/integration/template-engine/test-data/no-distribution-yaml/.gitkeep b/test/integration/template-engine/data/no-distribution-yaml/.gitkeep similarity index 100% rename from test/integration/template-engine/test-data/no-distribution-yaml/.gitkeep rename to test/integration/template-engine/data/no-distribution-yaml/.gitkeep diff --git a/test/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml b/test/integration/template-engine/data/no-furyctl-yaml/distribution.yaml similarity index 100% rename from test/integration/template-engine/test-data/no-furyctl-yaml/distribution.yaml rename to test/integration/template-engine/data/no-furyctl-yaml/distribution.yaml diff --git a/test/integration/template-engine/test-data/simple-dry-run/distribution.yaml b/test/integration/template-engine/data/simple-dry-run/distribution.yaml similarity index 100% rename from test/integration/template-engine/test-data/simple-dry-run/distribution.yaml rename to test/integration/template-engine/data/simple-dry-run/distribution.yaml diff --git a/test/integration/template-engine/test-data/simple-dry-run/furyctl.yaml b/test/integration/template-engine/data/simple-dry-run/furyctl.yaml similarity index 100% rename from test/integration/template-engine/test-data/simple-dry-run/furyctl.yaml rename to test/integration/template-engine/data/simple-dry-run/furyctl.yaml diff --git a/test/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl b/test/integration/template-engine/data/simple-dry-run/source/file.txt.tpl similarity index 100% rename from test/integration/template-engine/test-data/simple-dry-run/source/file.txt.tpl rename to test/integration/template-engine/data/simple-dry-run/source/file.txt.tpl diff --git a/test/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt b/test/integration/template-engine/data/simple-dry-run/source/keepfile.txt similarity index 100% rename from test/integration/template-engine/test-data/simple-dry-run/source/keepfile.txt rename to test/integration/template-engine/data/simple-dry-run/source/keepfile.txt diff --git a/test/integration/template-engine/test-data/simple/distribution.yaml b/test/integration/template-engine/data/simple/distribution.yaml similarity index 100% rename from test/integration/template-engine/test-data/simple/distribution.yaml rename to test/integration/template-engine/data/simple/distribution.yaml diff --git a/test/integration/template-engine/test-data/simple/furyctl.yaml b/test/integration/template-engine/data/simple/furyctl.yaml similarity index 100% rename from test/integration/template-engine/test-data/simple/furyctl.yaml rename to test/integration/template-engine/data/simple/furyctl.yaml diff --git a/test/integration/template-engine/test-data/simple/source/file.txt.tpl b/test/integration/template-engine/data/simple/source/file.txt.tpl similarity index 100% rename from test/integration/template-engine/test-data/simple/source/file.txt.tpl rename to test/integration/template-engine/data/simple/source/file.txt.tpl diff --git a/test/integration/template-engine/test-data/simple/source/keepfile.txt b/test/integration/template-engine/data/simple/source/keepfile.txt similarity index 100% rename from test/integration/template-engine/test-data/simple/source/keepfile.txt rename to test/integration/template-engine/data/simple/source/keepfile.txt diff --git a/test/integration/template-engine/tests.sh b/test/integration/template-engine/tests.sh index 0ed69043c..0e68f076b 100644 --- a/test/integration/template-engine/tests.sh +++ b/test/integration/template-engine/tests.sh @@ -31,9 +31,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "no distribution file" { info - test_dir="./test/integration/template-engine/test-data/no-distribution-yaml" + test_dir="./test/integration/template-engine/data/no-distribution-yaml" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug dump template } run init @@ -47,9 +47,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "no furyctl.yaml file" { info - test_dir="./test/integration/template-engine/test-data/no-furyctl-yaml" + test_dir="./test/integration/template-engine/data/no-furyctl-yaml" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug dump template } run init @@ -63,9 +63,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "no data property in distribution.yaml file" { info - test_dir="./test/integration/template-engine/test-data/distribution-yaml-no-data-property" + test_dir="./test/integration/template-engine/data/distribution-yaml-no-data-property" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug dump template } run init @@ -79,9 +79,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "empty template" { info - test_dir="./test/integration/template-engine/test-data/empty" + test_dir="./test/integration/template-engine/data/empty" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug dump template if [ -f ./target/file.txt ]; then false; else true; fi } run init @@ -94,9 +94,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "simple template dry-run" { info - test_dir="./test/integration/template-engine/test-data/simple-dry-run" + test_dir="./test/integration/template-engine/data/simple-dry-run" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template --dry-run + cd ${test_dir} && ${FURYCTL} -d --debug dump template --dry-run cat ./target/file.txt | grep "testValue" } run init @@ -109,9 +109,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "simple template" { info - test_dir="./test/integration/template-engine/test-data/simple" + test_dir="./test/integration/template-engine/data/simple" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug dump template cat ./target/file.txt | grep "testValue" } run init @@ -124,9 +124,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "complex template dry-run" { info - test_dir="./test/integration/template-engine/test-data/complex" + test_dir="./test/integration/template-engine/data/complex" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template --dry-run + cd ${test_dir} && ${FURYCTL} -d --debug dump template --dry-run # test that the config/example.yaml file has been generated if [ ! -f ./target/config/example.yaml ]; then @@ -163,9 +163,9 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "complex template" { info - test_dir="./test/integration/template-engine/test-data/complex" + test_dir="./test/integration/template-engine/data/complex" init(){ - cd ${test_dir} && ${FURYCTL} -d --debug template + cd ${test_dir} && ${FURYCTL} -d --debug dump template # test that the config/example.yaml file has been generated if [ ! -f ./target/config/example.yaml ]; then diff --git a/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml b/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl-defaults.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl-defaults.yaml rename to test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl-defaults.yaml diff --git a/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml b/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/furyctl.yaml rename to test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl.yaml diff --git a/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml b/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/kfd.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/kfd.yaml rename to test/integration/validation-cmd/data/config-invalid-furyctl-yaml/kfd.yaml diff --git a/test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json rename to test/integration/validation-cmd/data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json diff --git a/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml b/test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl-defaults.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl-defaults.yaml rename to test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl-defaults.yaml diff --git a/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml b/test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/furyctl.yaml rename to test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl.yaml diff --git a/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml b/test/integration/validation-cmd/data/config-valid-furyctl-yaml/kfd.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/kfd.yaml rename to test/integration/validation-cmd/data/config-valid-furyctl-yaml/kfd.yaml diff --git a/test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/test/integration/validation-cmd/data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/integration/validation-cmd/test-data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json rename to test/integration/validation-cmd/data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/ansible b/test/integration/validation-cmd/data/dependencies-correct/ansible similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/ansible rename to test/integration/validation-cmd/data/dependencies-correct/ansible diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/furyagent b/test/integration/validation-cmd/data/dependencies-correct/furyagent similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/furyagent rename to test/integration/validation-cmd/data/dependencies-correct/furyagent diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml b/test/integration/validation-cmd/data/dependencies-correct/furyctl.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/furyctl.yaml rename to test/integration/validation-cmd/data/dependencies-correct/furyctl.yaml diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml b/test/integration/validation-cmd/data/dependencies-correct/kfd.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/kfd.yaml rename to test/integration/validation-cmd/data/dependencies-correct/kfd.yaml diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/kubectl b/test/integration/validation-cmd/data/dependencies-correct/kubectl similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/kubectl rename to test/integration/validation-cmd/data/dependencies-correct/kubectl diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/kustomize b/test/integration/validation-cmd/data/dependencies-correct/kustomize similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/kustomize rename to test/integration/validation-cmd/data/dependencies-correct/kustomize diff --git a/test/integration/validation-cmd/test-data/dependencies-correct/terraform b/test/integration/validation-cmd/data/dependencies-correct/terraform similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-correct/terraform rename to test/integration/validation-cmd/data/dependencies-correct/terraform diff --git a/test/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml b/test/integration/validation-cmd/data/dependencies-missing/furyctl.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-missing/furyctl.yaml rename to test/integration/validation-cmd/data/dependencies-missing/furyctl.yaml diff --git a/test/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml b/test/integration/validation-cmd/data/dependencies-missing/kfd.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-missing/kfd.yaml rename to test/integration/validation-cmd/data/dependencies-missing/kfd.yaml diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/ansible b/test/integration/validation-cmd/data/dependencies-wrong/ansible similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/ansible rename to test/integration/validation-cmd/data/dependencies-wrong/ansible diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/furyagent b/test/integration/validation-cmd/data/dependencies-wrong/furyagent similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/furyagent rename to test/integration/validation-cmd/data/dependencies-wrong/furyagent diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml b/test/integration/validation-cmd/data/dependencies-wrong/furyctl.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/furyctl.yaml rename to test/integration/validation-cmd/data/dependencies-wrong/furyctl.yaml diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml b/test/integration/validation-cmd/data/dependencies-wrong/kfd.yaml similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/kfd.yaml rename to test/integration/validation-cmd/data/dependencies-wrong/kfd.yaml diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/kubectl b/test/integration/validation-cmd/data/dependencies-wrong/kubectl similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/kubectl rename to test/integration/validation-cmd/data/dependencies-wrong/kubectl diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/kustomize b/test/integration/validation-cmd/data/dependencies-wrong/kustomize similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/kustomize rename to test/integration/validation-cmd/data/dependencies-wrong/kustomize diff --git a/test/integration/validation-cmd/test-data/dependencies-wrong/terraform b/test/integration/validation-cmd/data/dependencies-wrong/terraform similarity index 100% rename from test/integration/validation-cmd/test-data/dependencies-wrong/terraform rename to test/integration/validation-cmd/data/dependencies-wrong/terraform diff --git a/test/integration/validation-cmd/tests.sh b/test/integration/validation-cmd/tests.sh index 4e30e17a0..bd0e90b1e 100644 --- a/test/integration/validation-cmd/tests.sh +++ b/test/integration/validation-cmd/tests.sh @@ -20,7 +20,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "furyctl" { info init(){ - "${FURYCTL} version" + "${FURYCTL}" version } run init if [[ ${status} -ne 0 ]]; then @@ -31,7 +31,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "invalid furyctl yaml" { info - test_dir="./test/integration/validation-cmd/test-data/config-invalid-furyctl-yaml" + test_dir="./test/integration/validation-cmd/data/config-invalid-furyctl-yaml" abs_test_dir=${PWD}/${test_dir} init(){ cd ${test_dir} && ${FURYCTL} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} @@ -48,7 +48,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "valid furyctl yaml" { info - test_dir="./test/integration/validation-cmd/test-data/config-valid-furyctl-yaml" + test_dir="./test/integration/validation-cmd/data/config-valid-furyctl-yaml" abs_test_dir=${PWD}/${test_dir} init(){ cd ${test_dir} && ${FURYCTL} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} @@ -60,7 +60,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "dependencies missing" { info - test_dir="./test/integration/validation-cmd/test-data/dependencies-missing" + test_dir="./test/integration/validation-cmd/data/dependencies-missing" abs_test_dir=${PWD}/${test_dir} init(){ cd ${test_dir} && \ @@ -87,7 +87,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "wrong dependencies installed" { info - test_dir="./test/integration/validation-cmd/test-data/dependencies-wrong" + test_dir="./test/integration/validation-cmd/data/dependencies-wrong" abs_test_dir=${PWD}/${test_dir} init(){ cd ${test_dir} && \ @@ -114,7 +114,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" @test "correct dependencies installed" { info - test_dir="./test/integration/validation-cmd/test-data/dependencies-correct" + test_dir="./test/integration/validation-cmd/data/dependencies-correct" abs_test_dir=${PWD}/${test_dir} init(){ export AWS_ACCESS_KEY_ID=foo From 873de0867c3ad55b3058fe87e2bcd45721cd0024 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:55:21 +0200 Subject: [PATCH 033/383] feat: rework semver package validation, add tests --- internal/semver/compare.go | 33 +++++----------- internal/semver/compare_test.go | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 internal/semver/compare_test.go diff --git a/internal/semver/compare.go b/internal/semver/compare.go index bdc03d56e..ddf67d0d6 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -6,11 +6,16 @@ package semver import ( "fmt" - "strconv" + "regexp" "strings" ) -var ErrInvalidSemver = fmt.Errorf("invalid semantic version") +var ( + // Link: https://regex101.com/r/Ly7O1x/3/ + regex = regexp.MustCompile(`^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) + + ErrInvalidSemver = fmt.Errorf("invalid semantic version") +) func NewVersion(v string) (Version, error) { if !isValid(v) { @@ -57,29 +62,9 @@ func SameMinor(a, b Version) bool { } func isValid(v string) bool { - if !strings.HasPrefix(v, "v") { - return false - } - - v = strings.TrimPrefix(v, "v") - - parts := strings.Split(v, ".") - - if len(parts) != 3 { - return false - } - - if _, err := strconv.Atoi(parts[0]); err != nil { - return false - } - - if _, err := strconv.Atoi(parts[1]); err != nil { - return false - } - - if _, err := strconv.Atoi(parts[2]); err != nil { + if v[0] != 'v' { return false } - return true + return regex.Match([]byte(v[1:])) } diff --git a/internal/semver/compare_test.go b/internal/semver/compare_test.go new file mode 100644 index 000000000..3c0a1b97c --- /dev/null +++ b/internal/semver/compare_test.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package semver_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/semver" +) + +func Test_NewVersion(t *testing.T) { + tests := []struct { + name string + version string + wantErr bool + }{ + { + name: "valid version", + version: "v1.2.3", + wantErr: false, + }, + { + name: "valid next version", + version: "v1.2.3-next", + wantErr: false, + }, + { + name: "valid alpha version", + version: "v1.2.3-alpha", + wantErr: false, + }, + { + name: "valid beta version", + version: "v1.2.3-beta.2", + wantErr: false, + }, + { + name: "valid rc version", + version: "v1.2.3-rc.3", + wantErr: false, + }, + { + name: "invalid v-less version", + version: "1.2.3", + wantErr: true, + }, + { + name: "invalid numeric version", + version: "11", + wantErr: true, + }, + { + name: "invalid string version", + version: "asd", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := semver.NewVersion(tt.version) + if (err != nil) != tt.wantErr { + t.Errorf("NewVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} From 139e299dd810effe84711df2bf79fe1f6a7458f6 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 19:55:42 +0200 Subject: [PATCH 034/383] chore: activate e2e tests on push --- .drone.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 95fecc87d..043b952f1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -81,9 +81,6 @@ steps: from_secret: AWS_REGION commands: - bats -t ./test/e2e/aws-eks/tests.sh - when: - event: - - tag - name: build-release image: ghcr.io/goreleaser/goreleaser:v1.11.4 From ef589f7c77bcb985f1e27dbab31707add3728ac6 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 20:03:24 +0200 Subject: [PATCH 035/383] chore: remove e2e tests --- .drone.yml | 23 --- test/e2e/aws-eks/.gitignore | 4 - test/e2e/aws-eks/bootstrap.tpl.yml | 29 --- test/e2e/aws-eks/cluster.tpl.yml | 42 ---- test/e2e/aws-eks/tests.sh | 295 ----------------------------- 5 files changed, 393 deletions(-) delete mode 100644 test/e2e/aws-eks/.gitignore delete mode 100644 test/e2e/aws-eks/bootstrap.tpl.yml delete mode 100644 test/e2e/aws-eks/cluster.tpl.yml delete mode 100644 test/e2e/aws-eks/tests.sh diff --git a/.drone.yml b/.drone.yml index 043b952f1..ac472fb02 100644 --- a/.drone.yml +++ b/.drone.yml @@ -59,29 +59,6 @@ steps: - bats -t ./test/integration/template-engine/tests.sh - bats -t ./test/integration/validation-cmd/tests.sh - - name: test-e2e - image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 - pull: always - privileged: true # Required to connect to the VPN - depends_on: - - build - environment: - CGO_ENABLED: 0 - FURYCTL_TOKEN: - from_secret: FURYCTL_TOKEN - TERRAFORM_TF_STATES_BUCKET_NAME: - from_secret: TERRAFORM_TF_STATES_BUCKET_NAME - AWS_ACCESS_KEY_ID: - from_secret: AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY: - from_secret: AWS_SECRET_ACCESS_KEY - AWS_REGION: - from_secret: AWS_REGION - AWS_DEFAULT_REGION: - from_secret: AWS_REGION - commands: - - bats -t ./test/e2e/aws-eks/tests.sh - - name: build-release image: ghcr.io/goreleaser/goreleaser:v1.11.4 pull: always diff --git a/test/e2e/aws-eks/.gitignore b/test/e2e/aws-eks/.gitignore deleted file mode 100644 index 242296826..000000000 --- a/test/e2e/aws-eks/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -cluster.yml -bootstrap.yml -cluster -bootstrap \ No newline at end of file diff --git a/test/e2e/aws-eks/bootstrap.tpl.yml b/test/e2e/aws-eks/bootstrap.tpl.yml deleted file mode 100644 index 5b529912f..000000000 --- a/test/e2e/aws-eks/bootstrap.tpl.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: e2e-${CI_BUILD_NUMBER} -spec: - networkCIDR: 10.0.0.0/16 - publicSubnetsCIDRs: - - 10.0.20.0/24 - - 10.0.30.0/24 - - 10.0.40.0/24 - privateSubnetsCIDRs: - - 10.0.182.0/24 - - 10.0.172.0/24 - - 10.0.162.0/24 - vpn: - subnetCIDR: 192.168.200.0/24 - sshUsers: - - jnardiello -provisioner: aws -executor: - state: - backend: s3 - config: - bucket: ${TERRAFORM_TF_STATES_BUCKET_NAME} - key: ${CI_REPO}/${DRONE_BRANCH}/${CI_BUILD_NUMBER}/bootstrap/aws.state - region: ${AWS_REGION} diff --git a/test/e2e/aws-eks/cluster.tpl.yml b/test/e2e/aws-eks/cluster.tpl.yml deleted file mode 100644 index 7deaa066c..000000000 --- a/test/e2e/aws-eks/cluster.tpl.yml +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: e2e-${CI_BUILD_NUMBER} -spec: - version: "1.25" - network: ${NETWORK_ID} - subnetworks: ${PRIVATE_SUBNETS} - dmzCIDRRange: ${NETWORK_CIDR} - sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCjjHeHnfhplyak6p+HaDnl7Vz8knFjUfgpvtx2FzfrzVmNWh7EuBWrisYeh/vwCFvluOQtt5/J56Gu+N5q70XAEeuh1COeeYlRm0EHZtm0dAM7PCvZ4Ga20PYWGJAGWiKo3g+jh2AexEXw+t6O9qvTy1G2OQ7uOGBfu+fa4tpBpGpHI0IrdwVJ6m1sd08ghmyjvWeIlxOwIF2SCcQqFosUngrvVieemEeojRRc7sedqUrehLEOX8udF+vLV8cRvzUMqrpmyLnEBRtcFzOhKMKiE+xlk9IKKWnMXYDhXlj4AFDQ19Yii2Z9uRUMVr/YVpDNvR7lBZo+EvRg0w5w9u9 - nodePoolsLaunchKind: "launch_templates" - nodePools: - - name: my-node-pool - minSize: 1 - maxSize: 1 - volumeSize: 50 - instanceType: t3.micro - additionalFirewallRules: - - name: dns - direction: ingress - cidrBlock: 0.0.0.0/0 - protocol: UDP - ports: 53-53 - tags: - allow: dns - - name: my-spot-pool - minSize: 1 - maxSize: 1 - volumeSize: 50 - instanceType: t3.micro - spotInstance: true -provisioner: eks -executor: - state: - backend: s3 - config: - bucket: ${TERRAFORM_TF_STATES_BUCKET_NAME} - key: ${CI_REPO}/${DRONE_BRANCH}/${CI_BUILD_NUMBER}/cluster/eks.state - region: ${AWS_REGION} diff --git a/test/e2e/aws-eks/tests.sh b/test/e2e/aws-eks/tests.sh deleted file mode 100644 index 168c26b24..000000000 --- a/test/e2e/aws-eks/tests.sh +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -load "./../../helper" - -OS="linux" -if [[ "$OSTYPE" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -FURYCTL=./dist/furyctl_${OS}_${CPUARCH}/furyctl - -@test "furyctl" { - info - init(){ - ${FURYCTL} --no-tty version - } - run init - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Prepare bootstrap.yml file" { - info - init(){ - envsubst < ./test/e2e/aws-eks/bootstrap.tpl.yml > ./test/e2e/aws-eks/bootstrap.yml - } - run init - [ "$status" -eq 0 ] -} - -@test "Bootstrap init" { - info - init(){ - ${FURYCTL} --no-tty -d --debug bootstrap init --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap --reset - } - run init - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Bootstrap apply (dry-run)" { - info - apply(){ - ${FURYCTL} --no-tty -d --debug bootstrap apply --dry-run --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./test/e2e/aws-eks/bootstrap/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Bootstrap apply" { - info - apply(){ - ${FURYCTL} --no-tty -d --debug bootstrap apply --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./test/e2e/aws-eks/bootstrap/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Create openvpn profile" { - info - apply(){ - furyagent configure openvpn-client --client-name "e2e-${CI_BUILD_NUMBER}" --config ./test/e2e/aws-eks/bootstrap/secrets/furyagent.yml > /tmp/e2e.ovpn - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Wait for openvpn instance SSH port open" { - info - check(){ - instance_ip=$(jq -r .vpn_ip.value[0] ./test/e2e/aws-eks/bootstrap/output/output.json) - echo " VPN Public IP: $instance_ip" >&3 - wait-for -t 60 "$instance_ip:22" -- echo "VPN Instance $instance_ip SSH Port (22) UP!" - } - run check - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Connect to the vpn" { - info - apply(){ - vpn-connect /tmp/e2e.ovpn - } - vpntest(){ - tuns=$(netstat -i | grep -c tun0) - if [ "$tuns" -eq 0 ]; then echo "VPN Connection not ready yet"; return 1; fi - } - run apply - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo "OVPN Profile: " >&3 - cat /tmp/e2e.ovpn >&3 - fi - [ "$status" -eq 0 ] - loop_it vpntest 60 5 - [ "$status" -eq 0 ] -} - -@test "Test Ping" { - info - check(){ - public_cidr=$(jq -r .public_subnets_cidr_blocks.value[0] ./test/e2e/aws-eks/bootstrap/output/output.json) - echo " Public CIDR: $public_cidr" >&3 - ips=$(nmap "$public_cidr" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b") - for ip in $ips; do - echo " Public (internal) ip discovered: $ip" >&3 - timeout 3 ping -c1 "$ip" - done - } - run check - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Prepare cluster.yml file" { - info - init(){ - PRIVATE_SUBNETS=$(jq -r .private_subnets.value ./test/e2e/aws-eks/bootstrap/output/output.json | tr -d '\n') - export PRIVATE_SUBNETS - NETWORK_ID=$(jq -r .vpc_id.value ./test/e2e/aws-eks/bootstrap/output/output.json) - export NETWORK_ID - NETWORK_CIDR=$(jq -r .vpc_cidr_block.value ./test/e2e/aws-eks/bootstrap/output/output.json) - export NETWORK_CIDR - envsubst < ./test/e2e/aws-eks/cluster.tpl.yml > ./test/e2e/aws-eks/cluster.yml - } - run init - [ "$status" -eq 0 ] -} - -@test "Cluster init" { - info - init(){ - ${FURYCTL} --no-tty -d --debug cluster init --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster --reset - } - run init - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster apply (dry-run)" { - info - apply(){ - ${FURYCTL} --no-tty -d --debug cluster apply --dry-run --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./test/e2e/aws-eks/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster apply" { - info - apply(){ - ${FURYCTL} --no-tty -d --debug cluster apply --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./test/e2e/aws-eks/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "kubectl get pods" { - info - cluster_info(){ - export KUBECONFIG=./test/e2e/aws-eks/cluster/secrets/kubeconfig - kubectl get pods -A >&3 - } - run cluster_info - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./test/e2e/aws-eks/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} - -@test "kubectl get nodes" { - info - cluster_info(){ - export KUBECONFIG=./test/e2e/aws-eks/cluster/secrets/kubeconfig - kubectl get nodes -o wide --show-labels >&3 - } - run cluster_info - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./test/e2e/aws-eks/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} - -@test "kubectl get nodes verify spot presence" { - info - test(){ - export KUBECONFIG=./test/e2e/aws-eks/cluster/secrets/kubeconfig - data=$(kubectl get nodes --show-labels | grep "node.kubernetes.io/lifecycle=spot") - if [ "${data}" == "" ]; then return 1; fi - } - loop_it test 60 5 - status=${loop_it_result} - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - cat ./test/e2e/aws-eks/cluster/secrets/kubeconfig >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Cluster destroy" { - info - destroy(){ - ${FURYCTL} --no-tty -d --debug cluster destroy --force --config ./test/e2e/aws-eks/cluster.yml -w ./test/e2e/aws-eks/cluster - } - run destroy - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./test/e2e/aws-eks/cluster/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Disconnect from the vpn" { - info - apply(){ - vpn-disconnect - } - run apply - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - fi - [ "$status" -eq 0 ] -} - -@test "Bootstrap destroy" { - info - destroy(){ - ${FURYCTL} --no-tty -d --debug bootstrap destroy --force --config ./test/e2e/aws-eks/bootstrap.yml -w ./test/e2e/aws-eks/bootstrap - } - run destroy - - if [[ $status -ne 0 ]]; then - echo "$output" >&3 - echo " TERRAFORM LOGS:" >&3 - cat ./test/e2e/aws-eks/bootstrap/logs/terraform.logs >&3 - fi - [ "$status" -eq 0 ] -} From 9b2f182dab049b612d314107657311c02170e537 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 20:07:51 +0200 Subject: [PATCH 036/383] chore: rename 'data' folder to 'configs', according to go standard layout --- {data => configs}/provisioners/bootstrap/aws/main.tf | 0 {data => configs}/provisioners/bootstrap/aws/output.tf | 0 {data => configs}/provisioners/bootstrap/aws/variables.tf | 0 {data => configs}/provisioners/cluster/eks/main.tf | 0 {data => configs}/provisioners/cluster/eks/output.tf | 0 {data => configs}/provisioners/cluster/eks/variables.tf | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {data => configs}/provisioners/bootstrap/aws/main.tf (100%) rename {data => configs}/provisioners/bootstrap/aws/output.tf (100%) rename {data => configs}/provisioners/bootstrap/aws/variables.tf (100%) rename {data => configs}/provisioners/cluster/eks/main.tf (100%) rename {data => configs}/provisioners/cluster/eks/output.tf (100%) rename {data => configs}/provisioners/cluster/eks/variables.tf (100%) diff --git a/data/provisioners/bootstrap/aws/main.tf b/configs/provisioners/bootstrap/aws/main.tf similarity index 100% rename from data/provisioners/bootstrap/aws/main.tf rename to configs/provisioners/bootstrap/aws/main.tf diff --git a/data/provisioners/bootstrap/aws/output.tf b/configs/provisioners/bootstrap/aws/output.tf similarity index 100% rename from data/provisioners/bootstrap/aws/output.tf rename to configs/provisioners/bootstrap/aws/output.tf diff --git a/data/provisioners/bootstrap/aws/variables.tf b/configs/provisioners/bootstrap/aws/variables.tf similarity index 100% rename from data/provisioners/bootstrap/aws/variables.tf rename to configs/provisioners/bootstrap/aws/variables.tf diff --git a/data/provisioners/cluster/eks/main.tf b/configs/provisioners/cluster/eks/main.tf similarity index 100% rename from data/provisioners/cluster/eks/main.tf rename to configs/provisioners/cluster/eks/main.tf diff --git a/data/provisioners/cluster/eks/output.tf b/configs/provisioners/cluster/eks/output.tf similarity index 100% rename from data/provisioners/cluster/eks/output.tf rename to configs/provisioners/cluster/eks/output.tf diff --git a/data/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf similarity index 100% rename from data/provisioners/cluster/eks/variables.tf rename to configs/provisioners/cluster/eks/variables.tf From 798101febda0ea6b3eb50d10a3f69b19838b6292 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 20 Sep 2022 20:09:19 +0200 Subject: [PATCH 037/383] fix: remove deps on a deleted step in drone manifest --- .drone.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index ac472fb02..19e00e8f0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -67,7 +67,6 @@ steps: - test - build - test-integration - - test-e2e environment: CGO_ENABLED: 0 GITHUB_TOKEN: From efb711ac4502eab7aa1717390e26925cfdf0c099 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:09:38 +0200 Subject: [PATCH 038/383] fix: lint issue --- internal/template/generator.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/template/generator.go b/internal/template/generator.go index cee8ff6d2..a1a8ffbbc 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -18,9 +18,7 @@ import ( "github.com/sighupio/furyctl/internal/io" ) -var ( - ErrProcessTemplate = errors.New("error processing template") -) +var ErrProcessTemplate = errors.New("error processing template") type generator struct { source string From 21ce535c777ca8d930820c6bc802fccfbf007a08 Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 21 Sep 2022 18:57:03 +0200 Subject: [PATCH 039/383] feat: refactor validate commands to extract their logic into app services --- cmd/validate.go | 3 +- cmd/validate/config.go | 151 +--- cmd/validate/dependencies.go | 338 +-------- cmd/validate/dependencies_test.go | 644 ------------------ internal/app/common_test.go | 34 + {cmd => internal/app}/validate/util.go | 93 ++- {cmd => internal/app}/validate/util_test.go | 15 +- internal/app/validate_config.go | 124 ++++ .../app/validate_config_test.go | 190 +++--- internal/app/validate_dependencies.go | 303 ++++++++ internal/app/validate_dependencies_test.go | 282 ++++++++ internal/execx/exec.go | 35 + 12 files changed, 980 insertions(+), 1232 deletions(-) delete mode 100644 cmd/validate/dependencies_test.go create mode 100644 internal/app/common_test.go rename {cmd => internal/app}/validate/util.go (53%) rename {cmd => internal/app}/validate/util_test.go (89%) create mode 100644 internal/app/validate_config.go rename cmd/validate/config_test.go => internal/app/validate_config_test.go (60%) create mode 100644 internal/app/validate_dependencies.go create mode 100644 internal/app/validate_dependencies_test.go create mode 100644 internal/execx/exec.go diff --git a/cmd/validate.go b/cmd/validate.go index 048792a26..418ca1dfd 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/sighupio/furyctl/cmd/validate" + "github.com/sighupio/furyctl/internal/execx" ) func NewValidateCommand(version string) *cobra.Command { @@ -17,7 +18,7 @@ func NewValidateCommand(version string) *cobra.Command { } validateCmd.AddCommand(validate.NewConfigCmd(version)) - validateCmd.AddCommand(validate.NewDependenciesCmd(version)) + validateCmd.AddCommand(validate.NewDependenciesCmd(version, execx.NewStdExecutor())) return validateCmd } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 20fc9395d..fd31c0f2c 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -6,22 +6,14 @@ package validate import ( "fmt" - "os" - "path/filepath" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/merge" - "github.com/sighupio/furyctl/internal/schema/santhosh" - "github.com/sighupio/furyctl/internal/semver" - "github.com/sighupio/furyctl/internal/yaml" ) -var errHasValidationErrors = fmt.Errorf("furyctl.yaml contains validation errors") - func NewConfigCmd(version string) *cobra.Command { cmd := &cobra.Command{ Use: "config", @@ -31,95 +23,29 @@ func NewConfigCmd(version string) *cobra.Command { furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) - if err != nil { - return err - } - - furyctlConfVersion := minimalConf.Spec.DistributionVersion - - if version != "dev" { - furyctlBinVersion, err := semver.NewVersion(fmt.Sprintf("v%s", version)) - if err != nil { - return err - } + vc := app.NewValidateConfig() - sameMinors := semver.SameMinor(furyctlConfVersion, furyctlBinVersion) - - if !sameMinors { - logrus.Warnf( - "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", - furyctlBinVersion, - furyctlConfVersion, - ) - } - } - - if distroLocation == "" { - distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfVersion.String()) - } - - repoPath, err := downloadDirectory(distroLocation) + res, err := vc.Execute(app.ValidateConfigRequest{ + FuryctlBinVersion: version, + DistroLocation: distroLocation, + FuryctlConfPath: furyctlPath, + Debug: debug, + }) if err != nil { return err } - if !debug { - defer cleanupTempDir(filepath.Base(repoPath)) - } - kfdPath := filepath.Join(repoPath, "kfd.yaml") - kfdManifest, err := yaml.FromFileV3[distribution.Manifest](kfdPath) - if err != nil { - return err - } + if res.HasErrors() { + logrus.Debugf("Repository path: %s", res.RepoPath) - if !semver.SamePatch(furyctlConfVersion, kfdManifest.Version) { - return fmt.Errorf( - "minor versions mismatch: furyctl.yaml has %s, but furyctl has %s", - furyctlConfVersion.String(), - kfdManifest.Version.String(), - ) - } + fmt.Println(res.Error) - schemaPath, err := getSchemaPath(repoPath, minimalConf) - if err != nil { - return err - } - - defaultPath := getDefaultPath(repoPath) - - defaultedFuryctlPath, err := mergeConfigAndDefaults(furyctlPath, defaultPath) - if err != nil { - return err - } - if !debug { - defer cleanupTempDir(filepath.Base(defaultedFuryctlPath)) + return nil } - schema, err := santhosh.LoadSchema(schemaPath) - if err != nil { - return err - } + fmt.Println("Config validation succeeded") - hasErrors := error(nil) - conf, err := yaml.FromFileV3[any](defaultedFuryctlPath) - if err != nil { - return err - } - - if err := schema.ValidateInterface(conf); err != nil { - logrus.Debugf("Config file: %s", defaultedFuryctlPath) - - fmt.Println(err) - - hasErrors = errHasValidationErrors - } - - if hasErrors == nil { - fmt.Println("Validation succeeded") - } - - return hasErrors + return nil }, } @@ -142,52 +68,3 @@ func NewConfigCmd(version string) *cobra.Command { return cmd } - -func mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { - defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) - if err != nil { - return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) - } - - furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) - if err != nil { - return "", fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) - } - - defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") - distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - - distroMerger := merge.NewMerger(defaultsModel, distributionModel) - - defaultedDistribution, err := distroMerger.Merge() - if err != nil { - return "", fmt.Errorf("%w: %v", ErrMergeDistroConfig, err) - } - - furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") - - furyctlMerger := merge.NewMerger(furyctlModel, defaultedDistributionModel) - - defaultedFuryctl, err := furyctlMerger.Merge() - if err != nil { - return "", fmt.Errorf("%w: %v", ErrMergeCompleteConfig, err) - } - - outYaml, err := yaml.MarshalV2(defaultedFuryctl) - if err != nil { - return "", fmt.Errorf("%w: %v", ErrYamlMarshalFile, err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") - if err != nil { - return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) - } - - confPath := filepath.Join(outDirPath, "config.yaml") - if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - return "", fmt.Errorf("%w: %v", ErrWriteFile, err) - } - - return confPath, nil -} diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 812164baa..ba59c29e7 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -5,32 +5,19 @@ package validate import ( - "errors" "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/semver" - "github.com/sighupio/furyctl/internal/yaml" + "github.com/sighupio/furyctl/internal/execx" ) -var ( - ErrSystemDepsValidation = errors.New("error while validating system dependencies") - ErrEnvironmentDepsValidation = errors.New("error while validating environment dependencies") - ErrEmptyToolVersion = errors.New("empty tool version") +var ErrDependencies = fmt.Errorf("dependencies are not satisfied") - execCommand = exec.Command -) - -func NewDependenciesCmd(version string) *cobra.Command { +func NewDependenciesCmd(version string, executor execx.Executor) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", Short: "Validate furyctl.yaml file", @@ -40,67 +27,30 @@ func NewDependenciesCmd(version string) *cobra.Command { furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlPath) - if err != nil { - return err - } - - furyctlConfVersion := minimalConf.Spec.DistributionVersion - - if version != "dev" { - furyctlBinVersion, err := semver.NewVersion(fmt.Sprintf("v%s", version)) - if err != nil { - return err - } - - sameMinors := semver.SameMinor(furyctlConfVersion, furyctlBinVersion) + vd := app.NewValidateDependencies(executor) - if !sameMinors { - logrus.Warnf( - "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", - furyctlBinVersion, - furyctlConfVersion, - ) - } - } - - if distroLocation == "" { - distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfVersion.String()) - } - - repoPath, err := downloadDirectory(distroLocation) + res, err := vd.Execute(app.ValidateDependenciesRequest{ + BinPath: binPath, + FuryctlBinVersion: version, + DistroLocation: distroLocation, + FuryctlConfPath: furyctlPath, + Debug: debug, + }) if err != nil { return err } - if !debug { - defer cleanupTempDir(filepath.Base(repoPath)) - } - kfdPath := filepath.Join(repoPath, "kfd.yaml") - kfdManifest, err := yaml.FromFileV3[distribution.Manifest](kfdPath) - if err != nil { - return err - } - - if !semver.SamePatch(furyctlConfVersion, kfdManifest.Version) { - return fmt.Errorf( - "versions mismatch: furyctl.yaml has %s, but furyctl has %s", - furyctlConfVersion.String(), - kfdManifest.Version.String(), - ) - } + if res.HasErrors() { + logrus.Debugf("Repository path: %s", res.RepoPath) - logrus.Debugln("Checking system dependencies") - if err := validateSystemDependencies(kfdManifest, binPath); err != nil { - return err - } + for _, err := range res.Errors { + logrus.Error(err) + } - logrus.Debugln("Checking environment dependencies") - if err := validateEnvDependencies(minimalConf.Kind); err != nil { - return err + return ErrDependencies } - fmt.Println("All dependencies are satisfied") + fmt.Println("Dependencies validation succeeded") return nil }, @@ -132,253 +82,3 @@ func NewDependenciesCmd(version string) *cobra.Command { return cmd } - -func validateEnvDependencies(kind distribution.Kind) error { - errs := make([]error, 0) - - if kind.Equals(distribution.EKSCluster) { - if os.Getenv("AWS_ACCESS_KEY_ID") == "" { - missingAccessKeyIdErr := fmt.Errorf("missing environment variable with key: AWS_ACCESS_KEY_ID") - logrus.Error(missingAccessKeyIdErr) - errs = append(errs, missingAccessKeyIdErr) - } - - if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { - missingSecretAccessKeyErr := fmt.Errorf("missing environment variable with key: AWS_SECRET_ACCESS_KEY") - logrus.Error(missingSecretAccessKeyErr) - errs = append(errs, missingSecretAccessKeyErr) - } - - if os.Getenv("AWS_DEFAULT_REGION") == "" { - missingDefaultRegionErr := fmt.Errorf("missing environment variable with key: AWS_DEFAULT_REGION") - logrus.Error(missingDefaultRegionErr) - errs = append(errs, missingDefaultRegionErr) - } - } - - if len(errs) > 0 { - return ErrEnvironmentDepsValidation - } - - return nil -} - -func validateSystemDependencies(kfdManifest distribution.Manifest, binPath string) error { - errs := make([]error, 0) - - if err := checkAnsibleVersion(kfdManifest.Tools.Ansible, binPath); err != nil { - logrus.Error(err) - errs = append(errs, err) - } - - if err := checkTerraformVersion(kfdManifest.Tools.Terraform, binPath); err != nil { - logrus.Error(err) - errs = append(errs, err) - } - - if err := checkKubectlVersion(kfdManifest.Tools.Kubectl, binPath); err != nil { - logrus.Error(err) - errs = append(errs, err) - } - - if err := checkKustomizeVersion(kfdManifest.Tools.Kustomize, binPath); err != nil { - logrus.Error(err) - errs = append(errs, err) - } - - if kfdManifest.Tools.Furyagent != "" { - if err := checkFuryagentVersion(kfdManifest.Tools.Furyagent, binPath); err != nil { - logrus.Error(err) - errs = append(errs, err) - } - } - - if len(errs) > 0 { - return ErrSystemDepsValidation - } - - return nil -} - -func checkAnsibleVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("ansible: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "ansible") - out, err := execCommand(path, "--version").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("ansible \\[.*]") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get ansible version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, " ") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get ansible version from system") - } - - systemAnsibleVersion := strings.TrimRight(versionStringTokens[len(versionStringTokens)-1], "]") - - if systemAnsibleVersion != wantVer { - return fmt.Errorf("ansible version on system: %s, required version: %s", systemAnsibleVersion, wantVer) - } - - return nil -} - -func checkTerraformVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("terraform: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "terraform") - out, err := execCommand(path, "--version").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("Terraform .*") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get terraform version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, " ") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get terraform version from system") - } - - systemTerraformVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") - - if systemTerraformVersion != wantVer { - return fmt.Errorf("terraform version on system: %s, required version: %s", systemTerraformVersion, wantVer) - } - - return nil -} - -func checkKubectlVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("kubectl: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "kubectl") - out, err := execCommand(path, "version", "--client").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("GitVersion:\"([^\"]*)\"") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get kubectl version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, ":") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get kubectl version from system") - } - - systemKubectlVersion := strings.TrimRight( - strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "\"v"), - "\"", - ) - - if systemKubectlVersion != wantVer { - return fmt.Errorf("kubectl version on system: %s, required version: %s", systemKubectlVersion, wantVer) - } - - return nil -} - -func checkKustomizeVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("kustomize: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "kustomize") - out, err := execCommand(path, "version", "--short").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("kustomize/v(\\S*)") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get kustomize version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, "/") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get kustomize version from system") - } - - systemKustomizeVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") - - if systemKustomizeVersion != wantVer { - return fmt.Errorf("kustomize version on system: %s, required version: %s", systemKustomizeVersion, wantVer) - } - - return nil -} - -func checkFuryagentVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("furyagent: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "furyagent") - out, err := execCommand(path, "version").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("version (\\S*)") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get furyagent version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, " ") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get furyagent version from system") - } - - systemFuryagentVersion := versionStringTokens[len(versionStringTokens)-1] - - if systemFuryagentVersion != wantVer { - return fmt.Errorf("furyagent version on system: %s, required version: %s", systemFuryagentVersion, wantVer) - } - - return nil -} diff --git a/cmd/validate/dependencies_test.go b/cmd/validate/dependencies_test.go deleted file mode 100644 index 3aaea5ea9..000000000 --- a/cmd/validate/dependencies_test.go +++ /dev/null @@ -1,644 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package validate - -import ( - "bytes" - "fmt" - "os" - "os/exec" - "path/filepath" - "testing" - - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/yaml" -) - -var ( - furyConfig = map[string]interface{}{ - "apiVersion": "kfd.sighup.io/v1alpha2", - "kind": "EKSCluster", - "spec": map[string]interface{}{ - "distributionVersion": "v1.24.7", - "distribution": map[string]interface{}{}, - }, - } - - kfdConfigCorrect = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{ - Ansible: "2.11.2", - Furyagent: "0.0.1", - Kubectl: "1.21.1", - Kustomize: "3.9.4", - Terraform: "0.15.4", - }, - } - - kfdConfigWrong = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{ - Ansible: "2.10.0", - Furyagent: "0.0.2", - Kubectl: "1.21.4", - Kustomize: "3.9.8", - Terraform: "0.15.9", - }, - } -) - -func TestNewDependenciesCmd_FailSysDepsValidation(t *testing.T) { - execCommand = fakeExecCommand - - defer func() { - execCommand = exec.Command - }() - - tmpDir, err := os.MkdirTemp("", "furyctl-deps-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - defer func() { - _ = os.RemoveAll(tmpDir) - }() - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(kfdConfigWrong) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - b := bytes.NewBufferString("") - valCmd := NewDependenciesCmd("dev") - - valCmd.SetOut(b) - - args := []string{"--config", configFilePath, "--distro-location", tmpDir} - valCmd.SetArgs(args) - - err = valCmd.Execute() - if err != ErrSystemDepsValidation { - t.Fatalf("want: %v, got: %v", ErrSystemDepsValidation, err) - } -} - -func TestNewDependenciesCmd_FailEnvVarsValidation(t *testing.T) { - execCommand = fakeExecCommand - - defer func() { - execCommand = exec.Command - }() - - tmpDir, err := os.MkdirTemp("", "furyctl-deps-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - defer func() { - _ = os.RemoveAll(tmpDir) - }() - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(kfdConfigCorrect) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - b := bytes.NewBufferString("") - valCmd := NewDependenciesCmd("dev") - - valCmd.SetOut(b) - - args := []string{"--config", configFilePath, "--distro-location", tmpDir} - valCmd.SetArgs(args) - - t.Setenv("AWS_ACCESS_KEY_ID", "") - t.Setenv("AWS_SECRET_ACCESS_KEY", "") - t.Setenv("AWS_DEFAULT_REGION", "") - - err = valCmd.Execute() - if err != ErrEnvironmentDepsValidation { - t.Fatalf("want: %v, got: %v", ErrEnvironmentDepsValidation, err) - } -} - -func TestNewDependenciesCmd_SuccessValidation(t *testing.T) { - execCommand = fakeExecCommand - - defer func() { - execCommand = exec.Command - }() - - tmpDir, err := os.MkdirTemp("", "furyctl-deps-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - defer func() { - _ = os.RemoveAll(tmpDir) - }() - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(kfdConfigCorrect) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - b := bytes.NewBufferString("") - valCmd := NewDependenciesCmd("dev") - - valCmd.SetOut(b) - - args := []string{"--config", configFilePath, "--distro-location", tmpDir} - valCmd.SetArgs(args) - - t.Setenv("AWS_ACCESS_KEY_ID", "test") - t.Setenv("AWS_SECRET_ACCESS_KEY", "test") - t.Setenv("AWS_DEFAULT_REGION", "test") - - err = valCmd.Execute() - if err != nil { - t.Fatalf("want: %v, got: %v", nil, err) - } -} - -func TestValidateEnvDependencies(t *testing.T) { - tests := []struct { - name string - kind distribution.Kind - setup func() - wantErr error - }{ - { - name: "test with correct dependencies: eks", - kind: "EKSCluster", - setup: func() { - t.Setenv("AWS_ACCESS_KEY_ID", "test") - t.Setenv("AWS_SECRET_ACCESS_KEY", "test") - t.Setenv("AWS_DEFAULT_REGION", "test") - }, - wantErr: nil, - }, - { - name: "test with incorrect dependencies: eks", - kind: "EKSCluster", - setup: func() { - t.Setenv("AWS_ACCESS_KEY_ID", "") - t.Setenv("AWS_SECRET_ACCESS_KEY", "") - t.Setenv("AWS_DEFAULT_REGION", "") - }, - wantErr: ErrEnvironmentDepsValidation, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setup() - err := validateEnvDependencies(tt.kind) - if err != nil { - if tt.wantErr == nil { - t.Errorf("validateEnvDependencies() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("validateEnvDependencies() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("validateEnvDependencies() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestValidateSystemDependencies(t *testing.T) { - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - tests := []struct { - name string - manifest distribution.Manifest - wantErr error - }{ - { - name: "test with correct dependencies", - manifest: kfdConfigCorrect, - wantErr: nil, - }, - { - name: "test with incorrect dependencies", - manifest: kfdConfigWrong, - wantErr: ErrSystemDepsValidation, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateSystemDependencies(tt.manifest, "") - if err != nil { - if tt.wantErr == nil { - t.Errorf("validateSystemDependencies() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("validateSystemDependencies() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("validateSystemDependencies() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestCheckAnsibleVersion(t *testing.T) { - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - tests := []struct { - name string - version string - want string - wantErr error - }{ - { - name: "test with correct ansible version", - version: "2.11.2", - want: "", - wantErr: nil, - }, - { - name: "test with incorrect ansible version", - version: "2.10.6", - want: "", - wantErr: fmt.Errorf("ansible version on system: 2.11.2, required version: 2.10.6"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := checkAnsibleVersion(tt.version, "") - if err != nil { - if tt.wantErr == nil { - t.Errorf("checkAnsibleVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("checkAnsibleVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("checkAnsibleVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestCheckTerraformVersion(t *testing.T) { - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - tests := []struct { - name string - version string - want string - wantErr error - }{ - { - name: "test with correct terraform version", - version: "0.15.4", - want: "", - wantErr: nil, - }, - { - name: "test with incorrect terraform version", - version: "0.14.6", - want: "", - wantErr: fmt.Errorf("terraform version on system: 0.15.4, required version: 0.14.6"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := checkTerraformVersion(tt.version, "") - if err != nil { - if tt.wantErr == nil { - t.Errorf("checkTerraformVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("checkTerraformVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("checkTerraformVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestCheckKubectlVersion(t *testing.T) { - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - tests := []struct { - name string - version string - want string - wantErr error - }{ - { - name: "test with correct kubectl version", - version: "1.21.1", - want: "", - wantErr: nil, - }, - { - name: "test with incorrect kubectl version", - version: "1.21.2", - want: "", - wantErr: fmt.Errorf("kubectl version on system: 1.21.1, required version: 1.21.2"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := checkKubectlVersion(tt.version, "") - if err != nil { - if tt.wantErr == nil { - t.Errorf("checkKubectlVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("checkKubectlVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("checkKubectlVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestCheckKustomizeVersion(t *testing.T) { - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - tests := []struct { - name string - version string - want string - wantErr error - }{ - { - name: "test with correct kustomize version", - version: "3.9.4", - want: "", - wantErr: nil, - }, - { - name: "test with incorrect kustomize version", - version: "3.9.3", - want: "", - wantErr: fmt.Errorf("kustomize version on system: 3.9.4, required version: 3.9.3"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := checkKustomizeVersion(tt.version, "") - if err != nil { - if tt.wantErr == nil { - t.Errorf("checkKustomizeVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("checkKustomizeVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("checkKustomizeVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestCheckFuryagentVersion(t *testing.T) { - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - tests := []struct { - name string - version string - want string - wantErr error - }{ - { - name: "test with correct furyagent version", - version: "0.0.1", - want: "", - wantErr: nil, - }, - { - name: "test with incorrect furyagent version", - version: "0.0.2", - want: "", - wantErr: fmt.Errorf("furyagent version on system: 0.0.1, required version: 0.0.2"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := checkFuryagentVersion(tt.version, "") - if err != nil { - if tt.wantErr == nil { - t.Errorf("checkFuryagentVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if err.Error() != tt.wantErr.Error() { - t.Errorf("checkFuryagentVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - } - - if err == nil && tt.wantErr != nil { - t.Errorf("checkFuryagentVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func fakeExecCommand(command string, args ...string) *exec.Cmd { - cs := []string{"-test.run=TestHelperProcess", "--", command} - cs = append(cs, args...) - cmd := exec.Command(os.Args[0], cs...) - return cmd -} - -func TestHelperProcess(t *testing.T) { - args := os.Args - - if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { - return - } - - cmd, _ := args[3], args[4:] - - switch cmd { - case "ansible": - fmt.Fprintf(os.Stdout, "ansible [core 2.11.2]\n "+ - "config file = None\n "+ - "configured module search path = ['', '']\n"+ - "ansible python module location = ./ansible\n"+ - "ansible collection location = ./ansible/collections\n"+ - "executable location = ./bin/ansible\n "+ - "python version = 3.9.14\n"+ - "jinja version = 3.1.2\n"+ - "libyaml = True\n") - case "terraform": - fmt.Fprintf(os.Stdout, "Terraform v0.15.4\non darwin_amd64") - case "kubectl": - fmt.Fprintf(os.Stdout, "Client Version: version.Info{Major:\"1\", "+ - "Minor:\"21\", GitVersion:\"v1.21.1\", GitCommit:\"xxxxx\", "+ - "GitTreeState:\"clean\", BuildDate:\"2021-05-12T14:00:00Z\", "+ - "GoVersion:\"go1.16.4\", Compiler:\"gc\", Platform:\"darwin/amd64\"}\n") - case "kustomize": - fmt.Fprintf(os.Stdout, "Version: {kustomize/v3.9.4 GitCommit:xxxxxxx"+ - "BuildDate:2021-05-12T14:00:00Z GoOs:darwin GoArch:amd64}") - case "furyagent": - fmt.Fprintf(os.Stdout, "furyagent version 0.0.1") - default: - fmt.Fprintf(os.Stdout, "command not found") - } - - os.Exit(0) -} diff --git a/internal/app/common_test.go b/internal/app/common_test.go new file mode 100644 index 000000000..04cf64cf9 --- /dev/null +++ b/internal/app/common_test.go @@ -0,0 +1,34 @@ +package app_test + +import ( + "os" + "testing" +) + +var furyConfig = map[string]interface{}{ + "apiVersion": "kfd.sighup.io/v1alpha2", + "kind": "EKSCluster", + "spec": map[string]interface{}{ + "distributionVersion": "v1.24.7", + "distribution": map[string]interface{}{}, + }, +} + +func mkDirTemp(t *testing.T, prefix string) string { + t.Helper() + + tmpDir, err := os.MkdirTemp("", prefix) + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + return tmpDir +} + +func rmDirTemp(t *testing.T, dir string) { + t.Helper() + + if err := os.RemoveAll(dir); err != nil { + t.Log(err) + } +} diff --git a/cmd/validate/util.go b/internal/app/validate/util.go similarity index 53% rename from cmd/validate/util.go rename to internal/app/validate/util.go index d71cfab08..4d16a302d 100644 --- a/cmd/validate/util.go +++ b/internal/app/validate/util.go @@ -15,6 +15,8 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/yaml" ) const DefaultBaseUrl = "https://git@github.com/sighupio/fury-distribution?ref=%s" @@ -26,16 +28,83 @@ var ( ErrCreatingTempDir = errors.New("error creating temp dir") ErrDownloadingFolder = errors.New("error downloading folder") - ErrHasValidationErrors = errors.New("schema has validation errors") ErrMergeCompleteConfig = errors.New("error merging complete config") ErrMergeDistroConfig = errors.New("error merging distribution config") - ErrUnknownOutputFormat = errors.New("unknown output format") ErrWriteFile = errors.New("error writing file") ErrYamlMarshalFile = errors.New("error marshaling yaml file") ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) -func getSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, error) { +type downloadResult struct { + RepoPath string + MinimalConf distribution.FuryctlConfig + DistroManifest distribution.Manifest +} + +func DownloadDistro( + version string, + distroLocation string, + furyctlConfPath string, + debug bool, +) (downloadResult, error) { + minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlConfPath) + if err != nil { + return downloadResult{}, err + } + + furyctlConfVersion := minimalConf.Spec.DistributionVersion + + if version != "unknown" { + furyctlBinVersion, err := semver.NewVersion(fmt.Sprintf("v%s", version)) + if err != nil { + return downloadResult{}, err + } + + sameMinors := semver.SameMinor(furyctlConfVersion, furyctlBinVersion) + + if !sameMinors { + logrus.Warnf( + "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", + furyctlBinVersion, + furyctlConfVersion, + ) + } + } + + if distroLocation == "" { + distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfVersion.String()) + } + + repoPath, err := DownloadDirectory(distroLocation) + if err != nil { + return downloadResult{}, err + } + if !debug { + defer CleanupTempDir(filepath.Base(repoPath)) + } + + kfdPath := filepath.Join(repoPath, "kfd.yaml") + kfdManifest, err := yaml.FromFileV3[distribution.Manifest](kfdPath) + if err != nil { + return downloadResult{}, err + } + + if !semver.SamePatch(furyctlConfVersion, kfdManifest.Version) { + return downloadResult{}, fmt.Errorf( + "minor versions mismatch: furyctl.yaml has %s, but furyctl has %s", + furyctlConfVersion.String(), + kfdManifest.Version.String(), + ) + } + + return downloadResult{ + RepoPath: repoPath, + MinimalConf: minimalConf, + DistroManifest: kfdManifest, + }, nil +} + +func GetSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, error) { avp := strings.Split(conf.ApiVersion, "/") if len(avp) < 2 { @@ -54,11 +123,11 @@ func getSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, er return filepath.Join(basePath, "schemas", filename), nil } -func getDefaultPath(basePath string) string { +func GetDefaultPath(basePath string) string { return filepath.Join(basePath, "furyctl-defaults.yaml") } -func downloadDirectory(src string) (string, error) { +func DownloadDirectory(src string) (string, error) { baseDst, err := os.MkdirTemp("", "furyctl-") if err != nil { return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) @@ -68,14 +137,14 @@ func downloadDirectory(src string) (string, error) { logrus.Debugf("Downloading '%s' in '%s'", src, dst) - if err := clientGet(src, dst); err != nil { + if err := ClientGet(src, dst); err != nil { return "", fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } return dst, nil } -func cleanupTempDir(dir string) { +func CleanupTempDir(dir string) { if err := os.RemoveAll(dir); err != nil { if !errors.Is(err, os.ErrNotExist) { logrus.Error(err) @@ -83,10 +152,10 @@ func cleanupTempDir(dir string) { } } -// clientGet tries a few different protocols to get the source file or directory. -func clientGet(src, dst string) error { +// ClientGet tries a few different protocols to get the source file or directory. +func ClientGet(src, dst string) error { protocols := []string{""} - if !urlHasForcedProtocol(src) { + if !UrlHasForcedProtocol(src) { protocols = downloadProtocols } @@ -112,8 +181,8 @@ func clientGet(src, dst string) error { return errDownloadOptionsExausted } -// urlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. -func urlHasForcedProtocol(url string) bool { +// UrlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. +func UrlHasForcedProtocol(url string) bool { for _, dp := range downloadProtocols { if dp != "" && strings.HasPrefix(url, dp) { return true diff --git a/cmd/validate/util_test.go b/internal/app/validate/util_test.go similarity index 89% rename from cmd/validate/util_test.go rename to internal/app/validate/util_test.go index e373181dc..42efd3490 100644 --- a/cmd/validate/util_test.go +++ b/internal/app/validate/util_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package validate +package validate_test import ( "fmt" @@ -10,6 +10,7 @@ import ( "path/filepath" "testing" + "github.com/sighupio/furyctl/internal/app/validate" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" ) @@ -81,17 +82,17 @@ func TestGetSchemaPath(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getSchemaPath(tt.basePath, tt.conf) + got, err := validate.GetSchemaPath(tt.basePath, tt.conf) if err != nil { if err.Error() != tt.wantErr.Error() { - t.Errorf("getSchemaPath() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("validate.GetSchemaPath() error = %v, wantErr %v", err, tt.wantErr) } return } if got != tt.want { - t.Errorf("getSchemaPath() = %v, want %v", got, tt.want) + t.Errorf("validate.GetSchemaPath() = %v, want %v", got, tt.want) } }) } @@ -113,7 +114,7 @@ func TestDownloadDirectory(t *testing.T) { _ = os.RemoveAll(tmpDir) }() - dlDir, err := downloadDirectory(tmpDir) + dlDir, err := validate.DownloadDirectory(tmpDir) if err != nil { t.Fatalf("error downloading directory: %v", err) } @@ -147,7 +148,7 @@ func TestClientGet(t *testing.T) { _ = os.RemoveAll(tmpDir) }() - err = clientGet(in, out) + err = validate.ClientGet(in, out) if err != nil { t.Fatalf("error getting directory: %v", err) } @@ -177,7 +178,7 @@ func TestUrlHasForcedProtocol(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := urlHasForcedProtocol(tt.url); got != tt.want { + if got := validate.UrlHasForcedProtocol(tt.url); got != tt.want { t.Errorf("urlHasForcedProtocol() = %v, want %v", got, tt.want) } }) diff --git a/internal/app/validate_config.go b/internal/app/validate_config.go new file mode 100644 index 000000000..fcfa2b600 --- /dev/null +++ b/internal/app/validate_config.go @@ -0,0 +1,124 @@ +package app + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/sighupio/furyctl/internal/app/validate" + "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/schema/santhosh" + "github.com/sighupio/furyctl/internal/yaml" +) + +type ValidateConfigRequest struct { + FuryctlBinVersion string + DistroLocation string + FuryctlConfPath string + Debug bool +} + +type ValidateConfigResponse struct { + Error error + RepoPath string +} + +func (v ValidateConfigResponse) HasErrors() bool { + return v.Error != nil +} + +func NewValidateConfig() *ValidateConfig { + return &ValidateConfig{} +} + +type ValidateConfig struct{} + +func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigResponse, error) { + res, err := validate.DownloadDistro(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + if err != nil { + return ValidateConfigResponse{}, err + } + + schemaPath, err := validate.GetSchemaPath(res.RepoPath, res.MinimalConf) + if err != nil { + return ValidateConfigResponse{}, err + } + + defaultPath := validate.GetDefaultPath(res.RepoPath) + + defaultedFuryctlConfPath, err := h.mergeConfigAndDefaults(req.FuryctlConfPath, defaultPath) + if err != nil { + return ValidateConfigResponse{}, err + } + if !req.Debug { + defer validate.CleanupTempDir(filepath.Base(defaultedFuryctlConfPath)) + } + + schema, err := santhosh.LoadSchema(schemaPath) + if err != nil { + return ValidateConfigResponse{}, err + } + + conf, err := yaml.FromFileV3[any](defaultedFuryctlConfPath) + if err != nil { + return ValidateConfigResponse{}, err + } + + if err := schema.ValidateInterface(conf); err != nil { + return ValidateConfigResponse{ + RepoPath: res.RepoPath, + Error: err, + }, nil + } + + return ValidateConfigResponse{}, nil +} + +func (h *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { + defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) + if err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrYamlUnmarshalFile, err) + } + + furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) + if err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrYamlUnmarshalFile, err) + } + + defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") + distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") + + distroMerger := merge.NewMerger(defaultsModel, distributionModel) + + defaultedDistribution, err := distroMerger.Merge() + if err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrMergeDistroConfig, err) + } + + furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") + defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") + + furyctlMerger := merge.NewMerger(furyctlModel, defaultedDistributionModel) + + defaultedFuryctl, err := furyctlMerger.Merge() + if err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrMergeCompleteConfig, err) + } + + outYaml, err := yaml.MarshalV2(defaultedFuryctl) + if err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrYamlMarshalFile, err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") + if err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrCreatingTempDir, err) + } + + confPath := filepath.Join(outDirPath, "config.yaml") + if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return "", fmt.Errorf("%w: %v", validate.ErrWriteFile, err) + } + + return confPath, nil +} diff --git a/cmd/validate/config_test.go b/internal/app/validate_config_test.go similarity index 60% rename from cmd/validate/config_test.go rename to internal/app/validate_config_test.go index e4d3c13a5..8be49b275 100644 --- a/cmd/validate/config_test.go +++ b/internal/app/validate_config_test.go @@ -1,33 +1,21 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package validate_test +package app_test import ( - "bytes" "encoding/json" - "fmt" + "errors" + "io/fs" "os" "path/filepath" "testing" - "github.com/sighupio/furyctl/cmd/validate" + "github.com/sighupio/furyctl/internal/app" + "github.com/sighupio/furyctl/internal/app/validate" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/yaml" ) var ( - furyConfig = map[string]interface{}{ - "apiVersion": "kfd.sighup.io/v1alpha2", - "kind": "EKSCluster", - "spec": map[string]interface{}{ - "distributionVersion": "v1.24.7", - "distribution": map[string]interface{}{}, - }, - } - - kfdConfig = distribution.Manifest{ + valConfKFDConf = distribution.Manifest{ Version: "v1.24.7", Modules: struct { Auth string `yaml:"auth"` @@ -58,7 +46,7 @@ var ( }{}, } - defaults = map[string]interface{}{ + valConfCorrectDefaults = map[string]interface{}{ "data": map[string]interface{}{ "modules": map[string]interface{}{ "ingress": map[string]interface{}{ @@ -68,7 +56,7 @@ var ( }, } - failingDefaults = map[string]interface{}{ + valConfWrongDefaults = map[string]interface{}{ "data": map[string]interface{}{ "modules": map[string]interface{}{ "ingress": map[string]interface{}{ @@ -79,7 +67,7 @@ var ( }, } - schema = map[string]interface{}{ + valConfSchema = map[string]interface{}{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://schema.sighup.io/kfd/1.23.2/ekscluster-kfd-v1alpha2.json", "type": "object", @@ -125,44 +113,34 @@ var ( } ) -func TestNewConfigCmd_ConfigNotFound(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - defer func() { - _ = os.RemoveAll(tmpDir) - }() +func TestValidateConfig_FuryctlConfigNotFound(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-config-validation-") + defer rmDirTemp(t, tmpDir) configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - expectedErr := fmt.Errorf("open %s: no such file or directory", configFilePath) - - b := bytes.NewBufferString("") - valCmd := validate.NewConfigCmd("dev") + vc := app.NewValidateConfig() + _, err := vc.Execute(app.ValidateConfigRequest{ + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) - valCmd.SetOut(b) + if err == nil { + t.Error("Expected error, got none") + } - args := []string{"--config", configFilePath} - valCmd.SetArgs(args) + var terr *fs.PathError - err = valCmd.Execute() - if err != nil && err.Error() != expectedErr.Error() { - t.Errorf("Expected error %v, got %v", expectedErr, err) - return + if !errors.As(err, &terr) { + t.Fatalf("Unexpected error: %v", err) } } -func TestNewConfigCmd_WrongDistroLocation(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - defer func() { - _ = os.RemoveAll(tmpDir) - }() +func TestValidateConfig_WrongDistroLocation(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-config-validation-") + defer rmDirTemp(t, tmpDir) configFilePath := filepath.Join(tmpDir, "furyctl.yaml") @@ -171,36 +149,30 @@ func TestNewConfigCmd_WrongDistroLocation(t *testing.T) { t.Fatalf("error marshaling config: %v", err) } - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { t.Fatalf("error writing config file: %v", err) } - expectedErr := fmt.Errorf("error downloading folder 'wrong-location': downloading options exausted") - - b := bytes.NewBufferString("") - valCmd := validate.NewConfigCmd("dev") - - valCmd.SetOut(b) - - args := []string{"--config", configFilePath, "--distro-location", "wrong-location"} - valCmd.SetArgs(args) + vc := app.NewValidateConfig() + _, err = vc.Execute(app.ValidateConfigRequest{ + FuryctlBinVersion: "unknown", + DistroLocation: "file::/tmp/does-not-exist", + FuryctlConfPath: configFilePath, + Debug: true, + }) - err = valCmd.Execute() - if err != nil && err.Error() != expectedErr.Error() { - t.Errorf("Expected error %v, got %v", expectedErr, err) - return + if err == nil { + t.Error("Expected error, got none") } -} -func TestNewConfigCmd_SuccessValidation(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) + if !errors.Is(err, validate.ErrDownloadingFolder) { + t.Fatalf("Unexpected error: %v", err) } +} - defer func() { - _ = os.RemoveAll(tmpDir) - }() +func TestValidateConfig_Success(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-config-validation-") + defer rmDirTemp(t, tmpDir) configFilePath := filepath.Join(tmpDir, "furyctl.yaml") @@ -209,13 +181,13 @@ func TestNewConfigCmd_SuccessValidation(t *testing.T) { t.Fatalf("error marshaling config: %v", err) } - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { t.Fatalf("error writing config file: %v", err) } kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - kfdYaml, err := yaml.MarshalV2(kfdConfig) + kfdYaml, err := yaml.MarshalV2(valConfKFDConf) if err != nil { t.Fatalf("error marshaling config: %v", err) } @@ -226,7 +198,7 @@ func TestNewConfigCmd_SuccessValidation(t *testing.T) { defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") - defaultsYaml, err := yaml.MarshalV2(defaults) + defaultsYaml, err := yaml.MarshalV2(valConfCorrectDefaults) if err != nil { t.Fatalf("error marshaling furyctl defaults: %v", err) } @@ -244,7 +216,7 @@ func TestNewConfigCmd_SuccessValidation(t *testing.T) { schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") - schemaJson, err := json.Marshal(schema) + schemaJson, err := json.Marshal(valConfSchema) if err != nil { t.Fatalf("error marshaling schema: %v", err) } @@ -253,30 +225,25 @@ func TestNewConfigCmd_SuccessValidation(t *testing.T) { t.Fatalf("error writing schema json: %v", err) } - b := bytes.NewBufferString("") - valCmd := validate.NewConfigCmd("dev") - - valCmd.SetOut(b) - - args := []string{"--config", configFilePath, "--distro-location", tmpDir} - valCmd.SetArgs(args) - - err = valCmd.Execute() + vc := app.NewValidateConfig() + res, err := vc.Execute(app.ValidateConfigRequest{ + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) if err != nil { - t.Errorf("Expected no error, got %v", err) - return + t.Fatalf("Unexpected error: %v", err) } -} -func TestNewConfigCmd_FailValidation(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-config-validation-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) + if res.Error != nil { + t.Fatalf("Unexpected validation error: %v", res.Error) } +} - defer func() { - _ = os.RemoveAll(tmpDir) - }() +func TestValidateConfig_Failure(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-config-validation-") + defer rmDirTemp(t, tmpDir) configFilePath := filepath.Join(tmpDir, "furyctl.yaml") @@ -285,13 +252,13 @@ func TestNewConfigCmd_FailValidation(t *testing.T) { t.Fatalf("error marshaling config: %v", err) } - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { t.Fatalf("error writing config file: %v", err) } kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - kfdYaml, err := yaml.MarshalV2(kfdConfig) + kfdYaml, err := yaml.MarshalV2(valConfKFDConf) if err != nil { t.Fatalf("error marshaling config: %v", err) } @@ -302,7 +269,7 @@ func TestNewConfigCmd_FailValidation(t *testing.T) { defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") - defaultsYaml, err := yaml.MarshalV2(failingDefaults) + defaultsYaml, err := yaml.MarshalV2(valConfWrongDefaults) if err != nil { t.Fatalf("error marshaling furyctl defaults: %v", err) } @@ -320,7 +287,7 @@ func TestNewConfigCmd_FailValidation(t *testing.T) { schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") - schemaJson, err := json.Marshal(schema) + schemaJson, err := json.Marshal(valConfSchema) if err != nil { t.Fatalf("error marshaling schema: %v", err) } @@ -329,19 +296,18 @@ func TestNewConfigCmd_FailValidation(t *testing.T) { t.Fatalf("error writing schema json: %v", err) } - b := bytes.NewBufferString("") - valCmd := validate.NewConfigCmd("dev") - - valCmd.SetOut(b) - - args := []string{"--config", configFilePath, "--distro-location", tmpDir} - valCmd.SetArgs(args) - - expectedError := "furyctl.yaml contains validation errors" + vc := app.NewValidateConfig() + res, err := vc.Execute(app.ValidateConfigRequest{ + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } - err = valCmd.Execute() - if err != nil && err.Error() != expectedError { - t.Errorf("Expected error %q, got %v", expectedError, err) - return + if res.Error == nil { + t.Fatalf("Expected validation errors, got none") } } diff --git a/internal/app/validate_dependencies.go b/internal/app/validate_dependencies.go new file mode 100644 index 000000000..dfe2faaac --- /dev/null +++ b/internal/app/validate_dependencies.go @@ -0,0 +1,303 @@ +package app + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/sighupio/furyctl/internal/app/validate" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/execx" +) + +var ( + ErrEmptyToolVersion = errors.New("empty tool version") + ErrMissingEnvVar = errors.New("missing environment variable") + ErrWrongToolVersion = errors.New("wrong tool version") +) + +type ValidateDependenciesRequest struct { + BinPath string + FuryctlBinVersion string + DistroLocation string + FuryctlConfPath string + Debug bool +} + +type ValidateDependenciesResponse struct { + Errors []error + RepoPath string +} + +func (vdr *ValidateDependenciesResponse) appendErrors(errs []error) { + vdr.Errors = append(vdr.Errors, errs...) +} + +func (vdr *ValidateDependenciesResponse) HasErrors() bool { + return len(vdr.Errors) > 0 +} + +func NewValidateDependencies(executor execx.Executor) *ValidateDependencies { + return &ValidateDependencies{executor: executor} +} + +type ValidateDependencies struct { + executor execx.Executor +} + +func (vd *ValidateDependencies) Execute(req ValidateDependenciesRequest) (ValidateDependenciesResponse, error) { + res := ValidateDependenciesResponse{} + + dres, err := validate.DownloadDistro(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + if err != nil { + return res, err + } + + res.RepoPath = dres.RepoPath + res.appendErrors(vd.validateSystemDependencies(dres.DistroManifest, req.BinPath)) + res.appendErrors(vd.validateEnvVarsDependencies(dres.MinimalConf.Kind)) + + return res, nil +} + +func (vd *ValidateDependencies) validateEnvVarsDependencies(kind distribution.Kind) []error { + errs := make([]error, 0) + + if kind.Equals(distribution.EKSCluster) { + if os.Getenv("AWS_ACCESS_KEY_ID") == "" { + errs = append(errs, fmt.Errorf("%w: AWS_ACCESS_KEY_ID", ErrMissingEnvVar)) + } + + if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { + errs = append(errs, fmt.Errorf("%w: AWS_SECRET_ACCESS_KEY", ErrMissingEnvVar)) + } + + if os.Getenv("AWS_DEFAULT_REGION") == "" { + errs = append(errs, fmt.Errorf("%w: AWS_DEFAULT_REGION", ErrMissingEnvVar)) + } + } + + if len(errs) > 0 { + return errs + } + + return nil +} + +func (vd *ValidateDependencies) validateSystemDependencies(kfdManifest distribution.Manifest, binPath string) []error { + errs := make([]error, 0) + + if err := vd.checkAnsibleVersion(kfdManifest.Tools.Ansible, binPath); err != nil { + errs = append(errs, err) + } + + if err := vd.checkTerraformVersion(kfdManifest.Tools.Terraform, binPath); err != nil { + errs = append(errs, err) + } + + if err := vd.checkKubectlVersion(kfdManifest.Tools.Kubectl, binPath); err != nil { + errs = append(errs, err) + } + + if err := vd.checkKustomizeVersion(kfdManifest.Tools.Kustomize, binPath); err != nil { + errs = append(errs, err) + } + + if kfdManifest.Tools.Furyagent != "" { + if err := vd.checkFuryagentVersion(kfdManifest.Tools.Furyagent, binPath); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return errs + } + + return nil +} + +func (vd *ValidateDependencies) checkAnsibleVersion(wantVer, binPath string) error { + if wantVer == "" { + return fmt.Errorf("ansible: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "ansible") + out, err := vd.executor.Command(path, "--version").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("ansible \\[.*]") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get ansible version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, " ") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get ansible version from system") + } + + systemAnsibleVersion := strings.TrimRight(versionStringTokens[len(versionStringTokens)-1], "]") + + if systemAnsibleVersion != wantVer { + return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemAnsibleVersion, wantVer) + } + + return nil +} + +func (vd *ValidateDependencies) checkTerraformVersion(wantVer, binPath string) error { + if wantVer == "" { + return fmt.Errorf("terraform: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "terraform") + out, err := vd.executor.Command(path, "--version").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("Terraform .*") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get terraform version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, " ") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get terraform version from system") + } + + systemTerraformVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") + + if systemTerraformVersion != wantVer { + return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemTerraformVersion, wantVer) + } + + return nil +} + +func (vd *ValidateDependencies) checkKubectlVersion(wantVer, binPath string) error { + if wantVer == "" { + return fmt.Errorf("kubectl: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "kubectl") + out, err := vd.executor.Command(path, "version", "--client").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("GitVersion:\"([^\"]*)\"") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get kubectl version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, ":") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get kubectl version from system") + } + + systemKubectlVersion := strings.TrimRight( + strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "\"v"), + "\"", + ) + + if systemKubectlVersion != wantVer { + return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemKubectlVersion, wantVer) + } + + return nil +} + +func (vd *ValidateDependencies) checkKustomizeVersion(wantVer, binPath string) error { + if wantVer == "" { + return fmt.Errorf("kustomize: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "kustomize") + out, err := vd.executor.Command(path, "version", "--short").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("kustomize/v(\\S*)") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get kustomize version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, "/") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get kustomize version from system") + } + + systemKustomizeVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") + + if systemKustomizeVersion != wantVer { + return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemKustomizeVersion, wantVer) + } + + return nil +} + +func (vd *ValidateDependencies) checkFuryagentVersion(wantVer, binPath string) error { + if wantVer == "" { + return fmt.Errorf("furyagent: %w", ErrEmptyToolVersion) + } + + path := filepath.Join(binPath, "furyagent") + out, err := vd.executor.Command(path, "version").Output() + if err != nil { + return err + } + + s := string(out) + + pattern := regexp.MustCompile("version (\\S*)") + + versionStringIndex := pattern.FindStringIndex(s) + if versionStringIndex == nil { + return fmt.Errorf("can't get furyagent version from system") + } + + versionString := s[versionStringIndex[0]:versionStringIndex[1]] + + versionStringTokens := strings.Split(versionString, " ") + if len(versionStringTokens) == 0 { + return fmt.Errorf("can't get furyagent version from system") + } + + systemFuryagentVersion := versionStringTokens[len(versionStringTokens)-1] + + if systemFuryagentVersion != wantVer { + return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemFuryagentVersion, wantVer) + } + + return nil +} diff --git a/internal/app/validate_dependencies_test.go b/internal/app/validate_dependencies_test.go new file mode 100644 index 000000000..afab55da1 --- /dev/null +++ b/internal/app/validate_dependencies_test.go @@ -0,0 +1,282 @@ +package app_test + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/sighupio/furyctl/internal/app" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/execx" + "github.com/sighupio/furyctl/internal/yaml" +) + +var ( + valDepWrongKFDConf = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{ + Ansible: "2.10.0", + Furyagent: "0.0.2", + Kubectl: "1.21.4", + Kustomize: "3.9.8", + Terraform: "0.15.9", + }, + } + + valDepCorrectKFDConf = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{ + Ansible: "2.11.2", + Furyagent: "0.0.1", + Kubectl: "1.21.1", + Kustomize: "3.9.4", + Terraform: "0.15.4", + }, + } +) + +func TestValidateDependencies_MissingToolsAndEnvs(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-deps-validation-") + defer rmDirTemp(t, tmpDir) + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(valDepCorrectKFDConf) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + vd := app.NewValidateDependencies(execx.NewStdExecutor()) + + res, err := vd.Execute(app.ValidateDependenciesRequest{ + BinPath: filepath.Join(tmpDir, "bin"), + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + + if len(res.Errors) == 0 { + t.Fatal("Expected validation errors, got none") + } + + if len(res.Errors) != 8 { + t.Fatalf("Expected 8 validation errors, got %d", len(res.Errors)) + } + + for _, err := range res.Errors { + var terr *fs.PathError + + if !errors.As(err, &terr) && !errors.Is(err, app.ErrMissingEnvVar) { + t.Fatalf("Unexpected error: %v", err) + } + } +} + +func TestValidateDependencies_HasAllToolsAndEnvs(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-deps-validation-") + defer rmDirTemp(t, tmpDir) + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(valDepCorrectKFDConf) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + t.Setenv("AWS_ACCESS_KEY_ID", "test") + t.Setenv("AWS_SECRET_ACCESS_KEY", "test") + t.Setenv("AWS_DEFAULT_REGION", "test") + + vd := app.NewValidateDependencies(execx.NewFakeExecutor()) + + res, err := vd.Execute(app.ValidateDependenciesRequest{ + BinPath: filepath.Join(tmpDir, "bin"), + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + + if len(res.Errors) != 0 { + t.Errorf("Did not expect validation errors, got %d", len(res.Errors)) + for _, err := range res.Errors { + t.Log(err) + } + } +} + +func TestValidateDependencies_HasWrongTools(t *testing.T) { + tmpDir := mkDirTemp(t, "furyctl-deps-validation-") + defer rmDirTemp(t, tmpDir) + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(valDepWrongKFDConf) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + t.Setenv("AWS_ACCESS_KEY_ID", "test") + t.Setenv("AWS_SECRET_ACCESS_KEY", "test") + t.Setenv("AWS_DEFAULT_REGION", "test") + + vd := app.NewValidateDependencies(execx.NewFakeExecutor()) + + res, err := vd.Execute(app.ValidateDependenciesRequest{ + BinPath: filepath.Join(tmpDir, "bin"), + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + + if len(res.Errors) != 5 { + t.Fatalf("Expected 5 validation errors, got %d", len(res.Errors)) + } + + for _, err := range res.Errors { + if !errors.Is(err, app.ErrWrongToolVersion) { + t.Errorf("Unexpected error: %v", err) + } + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, _ := args[3], args[4:] + + switch cmd { + case "ansible": + fmt.Fprintf(os.Stdout, "ansible [core 2.11.2]\n "+ + "config file = None\n "+ + "configured module search path = ['', '']\n"+ + "ansible python module location = ./ansible\n"+ + "ansible collection location = ./ansible/collections\n"+ + "executable location = ./bin/ansible\n "+ + "python version = 3.9.14\n"+ + "jinja version = 3.1.2\n"+ + "libyaml = True\n") + case "terraform": + fmt.Fprintf(os.Stdout, "Terraform v0.15.4\non darwin_amd64") + case "kubectl": + fmt.Fprintf(os.Stdout, "Client Version: version.Info{Major:\"1\", "+ + "Minor:\"21\", GitVersion:\"v1.21.1\", GitCommit:\"xxxxx\", "+ + "GitTreeState:\"clean\", BuildDate:\"2021-05-12T14:00:00Z\", "+ + "GoVersion:\"go1.16.4\", Compiler:\"gc\", Platform:\"darwin/amd64\"}\n") + case "kustomize": + fmt.Fprintf(os.Stdout, "Version: {kustomize/v3.9.4 GitCommit:xxxxxxx"+ + "BuildDate:2021-05-12T14:00:00Z GoOs:darwin GoArch:amd64}") + case "furyagent": + fmt.Fprintf(os.Stdout, "furyagent version 0.0.1") + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} diff --git a/internal/execx/exec.go b/internal/execx/exec.go new file mode 100644 index 000000000..12723ed96 --- /dev/null +++ b/internal/execx/exec.go @@ -0,0 +1,35 @@ +package execx + +import ( + "os" + "os/exec" + "path/filepath" +) + +type Executor interface { + Command(name string, arg ...string) *exec.Cmd +} + +func NewStdExecutor() *StdExecutor { + return &StdExecutor{} +} + +type StdExecutor struct{} + +func (e *StdExecutor) Command(name string, arg ...string) *exec.Cmd { + return exec.Command(name, arg...) +} + +func NewFakeExecutor() *FakeExecutor { + return &FakeExecutor{} +} + +type FakeExecutor struct{} + +func (e *FakeExecutor) Command(path string, arg ...string) *exec.Cmd { + name := filepath.Base(path) + cs := []string{"-test.run=TestHelperProcess", "--", name} + cs = append(cs, arg...) + + return exec.Command(os.Args[0], cs...) +} From a7fd0e9166f6a55766f501fdf18cf76ab4ca4349 Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 21 Sep 2022 20:16:47 +0200 Subject: [PATCH 040/383] chore: refactor validate tests --- internal/app/common_test.go | 177 ++++++++- internal/app/validate_config_test.go | 422 ++++++++------------- internal/app/validate_dependencies_test.go | 302 ++++----------- 3 files changed, 398 insertions(+), 503 deletions(-) diff --git a/internal/app/common_test.go b/internal/app/common_test.go index 04cf64cf9..2f9f98ea0 100644 --- a/internal/app/common_test.go +++ b/internal/app/common_test.go @@ -1,18 +1,120 @@ package app_test import ( + "encoding/json" "os" + "path/filepath" "testing" + + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/yaml" ) -var furyConfig = map[string]interface{}{ - "apiVersion": "kfd.sighup.io/v1alpha2", - "kind": "EKSCluster", - "spec": map[string]interface{}{ - "distributionVersion": "v1.24.7", - "distribution": map[string]interface{}{}, - }, -} +var ( + furyConfig = map[string]interface{}{ + "apiVersion": "kfd.sighup.io/v1alpha2", + "kind": "EKSCluster", + "spec": map[string]interface{}{ + "distributionVersion": "v1.24.7", + "distribution": map[string]interface{}{}, + }, + } + + correctFuryctlDefaults = map[string]any{ + "data": map[string]any{ + "modules": map[string]any{ + "ingress": map[string]any{ + "test": "test", + }, + }, + }, + } + + wrongFuryctlDefaults = map[string]any{ + "data": map[string]any{ + "modules": map[string]any{ + "ingress": map[string]any{ + "test": "test", + "unexpected": "test", + }, + }, + }, + } + + correctKFDConf = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{ + Ansible: "2.11.2", + Furyagent: "0.0.1", + Kubectl: "1.21.1", + Kustomize: "3.9.4", + Terraform: "0.15.4", + }, + } + + wrongKFDConf = distribution.Manifest{ + Version: "v1.24.7", + Modules: struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` + }{}, + Kubernetes: struct { + Eks struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` + } `yaml:"eks"` + }{}, + FuryctlSchemas: struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` + }{}, + Tools: struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` + }{ + Ansible: "2.10.0", + Furyagent: "0.0.2", + Kubectl: "1.21.4", + Kustomize: "3.9.8", + Terraform: "0.15.9", + }, + } +) func mkDirTemp(t *testing.T, prefix string) string { t.Helper() @@ -32,3 +134,62 @@ func rmDirTemp(t *testing.T, dir string) { t.Log(err) } } + +func setupDistroFolder(t *testing.T, furyctlDefaults map[string]any, kfdConf distribution.Manifest) (string, string) { + t.Helper() + + tmpDir := mkDirTemp(t, "furyctl-validate-test-") + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } + + kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") + + kfdYaml, err := yaml.MarshalV2(kfdConf) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } + + if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { + t.Fatalf("error writing kfd file: %v", err) + } + + defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") + + defaultsYaml, err := yaml.MarshalV2(furyctlDefaults) + if err != nil { + t.Fatalf("error marshaling furyctl defaults: %v", err) + } + + if err = os.WriteFile(defaultsFilePath, defaultsYaml, os.ModePerm); err != nil { + t.Fatalf("error writing furyctl defaults: %v", err) + } + + schemasPath := filepath.Join(tmpDir, "schemas") + + err = os.Mkdir(schemasPath, os.ModePerm) + if err != nil { + t.Fatalf("error creating schemas dir: %v", err) + } + + schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") + + schemaJson, err := json.Marshal(eksClusterJsonSchema) + if err != nil { + t.Fatalf("error marshaling schema: %v", err) + } + + if err = os.WriteFile(schemaFilePath, schemaJson, os.ModePerm); err != nil { + t.Fatalf("error writing schema json: %v", err) + } + + return tmpDir, configFilePath +} diff --git a/internal/app/validate_config_test.go b/internal/app/validate_config_test.go index 8be49b275..47b568d72 100644 --- a/internal/app/validate_config_test.go +++ b/internal/app/validate_config_test.go @@ -1,106 +1,52 @@ package app_test import ( - "encoding/json" "errors" "io/fs" "os" "path/filepath" "testing" + "github.com/santhosh-tekuri/jsonschema" + "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/app/validate" - "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/yaml" ) -var ( - valConfKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{}, - } - - valConfCorrectDefaults = map[string]interface{}{ - "data": map[string]interface{}{ - "modules": map[string]interface{}{ - "ingress": map[string]interface{}{ - "test": "test", - }, - }, +var eksClusterJsonSchema = map[string]any{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/1.23.2/ekscluster-kfd-v1alpha2.json", + "type": "object", + "additionalProperties": false, + "properties": map[string]any{ + "apiVersion": map[string]any{ + "type": "string", }, - } - - valConfWrongDefaults = map[string]interface{}{ - "data": map[string]interface{}{ - "modules": map[string]interface{}{ - "ingress": map[string]interface{}{ - "test": "test", - "unexpected": "test", - }, - }, + "kind": map[string]any{ + "type": "string", }, - } - - valConfSchema = map[string]interface{}{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/1.23.2/ekscluster-kfd-v1alpha2.json", - "type": "object", - "additionalProperties": false, - "properties": map[string]interface{}{ - "apiVersion": map[string]interface{}{ - "type": "string", - }, - "kind": map[string]interface{}{ - "type": "string", - }, - "spec": map[string]interface{}{ - "type": "object", - "additionalProperties": false, - "properties": map[string]interface{}{ - "distributionVersion": map[string]interface{}{ - "type": "string", - }, - "distribution": map[string]interface{}{ - "type": "object", - "additionalProperties": false, - "properties": map[string]interface{}{ - "modules": map[string]interface{}{ - "type": "object", - "additionalProperties": false, - "properties": map[string]interface{}{ - "ingress": map[string]interface{}{ - "type": "object", - "additionalProperties": false, - "properties": map[string]interface{}{ - "test": map[string]interface{}{ - "type": "string", - }, + "spec": map[string]any{ + "type": "object", + "additionalProperties": false, + "properties": map[string]any{ + "distributionVersion": map[string]any{ + "type": "string", + }, + "distribution": map[string]any{ + "type": "object", + "additionalProperties": false, + "properties": map[string]any{ + "modules": map[string]any{ + "type": "object", + "additionalProperties": false, + "properties": map[string]any{ + "ingress": map[string]any{ + "type": "object", + "additionalProperties": false, + "properties": map[string]any{ + "test": map[string]any{ + "type": "string", }, }, }, @@ -110,204 +56,136 @@ var ( }, }, }, - } -) - -func TestValidateConfig_FuryctlConfigNotFound(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-config-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - vc := app.NewValidateConfig() - _, err := vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - - if err == nil { - t.Error("Expected error, got none") - } - - var terr *fs.PathError - - if !errors.As(err, &terr) { - t.Fatalf("Unexpected error: %v", err) - } -} - -func TestValidateConfig_WrongDistroLocation(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-config-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - vc := app.NewValidateConfig() - _, err = vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: "unknown", - DistroLocation: "file::/tmp/does-not-exist", - FuryctlConfPath: configFilePath, - Debug: true, - }) - - if err == nil { - t.Error("Expected error, got none") - } - - if !errors.Is(err, validate.ErrDownloadingFolder) { - t.Fatalf("Unexpected error: %v", err) - } -} - -func TestValidateConfig_Success(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-config-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(valConfKFDConf) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") - - defaultsYaml, err := yaml.MarshalV2(valConfCorrectDefaults) - if err != nil { - t.Fatalf("error marshaling furyctl defaults: %v", err) - } - - if err = os.WriteFile(defaultsFilePath, defaultsYaml, os.ModePerm); err != nil { - t.Fatalf("error writing furyctl defaults: %v", err) - } - - schemasPath := filepath.Join(tmpDir, "schemas") - - err = os.Mkdir(schemasPath, os.ModePerm) - if err != nil { - t.Fatalf("error creating schemas dir: %v", err) - } - - schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") - - schemaJson, err := json.Marshal(valConfSchema) - if err != nil { - t.Fatalf("error marshaling schema: %v", err) - } - - if err = os.WriteFile(schemaFilePath, schemaJson, os.ModePerm); err != nil { - t.Fatalf("error writing schema json: %v", err) - } - - vc := app.NewValidateConfig() - res, err := vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if res.Error != nil { - t.Fatalf("Unexpected validation error: %v", res.Error) - } + }, } -func TestValidateConfig_Failure(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-config-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") +func TestValidateConfig(t *testing.T) { + testCases := []struct { + desc string + setup func(t *testing.T) (string, string) + teardown func(t *testing.T, tmpDir string) + wantErr bool + wantErrVal any + wantErrType error + wantValidationErr bool + wantValidationErrVal any + }{ + { + desc: "furyctl.yaml not found", + setup: func(t *testing.T) (string, string) { + t.Helper() + + tmpDir := mkDirTemp(t, "furyctl-config-validation-") + + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") + + return tmpDir, configFilePath + }, + teardown: func(t *testing.T, tmpDir string) { + t.Helper() - kfdYaml, err := yaml.MarshalV2(valConfKFDConf) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } + rmDirTemp(t, tmpDir) + }, + wantErr: true, + wantErrVal: &fs.PathError{}, + }, + { + desc: "wrong distro location", + setup: func(t *testing.T) (string, string) { + t.Helper() - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } + tmpDir := mkDirTemp(t, "furyctl-config-validation-") - defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") + configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - defaultsYaml, err := yaml.MarshalV2(valConfWrongDefaults) - if err != nil { - t.Fatalf("error marshaling furyctl defaults: %v", err) - } + configYaml, err := yaml.MarshalV2(furyConfig) + if err != nil { + t.Fatalf("error marshaling config: %v", err) + } - if err = os.WriteFile(defaultsFilePath, defaultsYaml, os.ModePerm); err != nil { - t.Fatalf("error writing furyctl defaults: %v", err) - } + if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { + t.Fatalf("error writing config file: %v", err) + } - schemasPath := filepath.Join(tmpDir, "schemas") + return "file::/tmp/does-not-exist", configFilePath + }, + teardown: func(t *testing.T, tmpDir string) { + t.Helper() - err = os.Mkdir(schemasPath, os.ModePerm) - if err != nil { - t.Fatalf("error creating schemas dir: %v", err) - } + rmDirTemp(t, tmpDir) + }, + wantErr: true, + wantErrType: validate.ErrDownloadingFolder, + }, + { + desc: "success", + setup: func(t *testing.T) (string, string) { + t.Helper() - schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") + return setupDistroFolder(t, correctFuryctlDefaults, correctKFDConf) + }, + teardown: func(t *testing.T, tmpDir string) { + t.Helper() - schemaJson, err := json.Marshal(valConfSchema) - if err != nil { - t.Fatalf("error marshaling schema: %v", err) - } + rmDirTemp(t, tmpDir) + }, + }, + { + desc: "failure", + setup: func(t *testing.T) (string, string) { + t.Helper() - if err = os.WriteFile(schemaFilePath, schemaJson, os.ModePerm); err != nil { - t.Fatalf("error writing schema json: %v", err) - } + return setupDistroFolder(t, wrongFuryctlDefaults, correctKFDConf) + }, + teardown: func(t *testing.T, tmpDir string) { + t.Helper() - vc := app.NewValidateConfig() - res, err := vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) + rmDirTemp(t, tmpDir) + }, + wantValidationErr: true, + wantValidationErrVal: &jsonschema.ValidationError{}, + }, } - - if res.Error == nil { - t.Fatalf("Expected validation errors, got none") + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + tmpDir, configFilePath := tC.setup(t) + + defer tC.teardown(t, tmpDir) + + vc := app.NewValidateConfig() + res, err := vc.Execute(app.ValidateConfigRequest{ + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + + if tC.wantErr && err == nil { + t.Error("expected error, got nil") + } + if !tC.wantErr && err != nil { + t.Errorf("unexpected error, got = %v", err) + } + + if tC.wantErrVal != nil && !errors.As(err, &tC.wantErrVal) { + t.Fatalf("got error = %v, want = %v", err, tC.wantErrVal) + } + if tC.wantErrType != nil && !errors.Is(err, tC.wantErrType) { + t.Fatalf("got error = %v, want = %v", err, tC.wantErrType) + } + + if tC.wantValidationErr && res.Error == nil { + t.Fatal("expected validation error, got nil") + } + if !tC.wantValidationErr && res.Error != nil { + t.Fatalf("unexpected validation error, got = %v", res.Error) + } + + if tC.wantValidationErrVal != nil && !errors.As(res.Error, &tC.wantValidationErrVal) { + t.Fatalf("got validation error = %v, want = %v", res.Error, tC.wantValidationErrVal) + } + }) } } diff --git a/internal/app/validate_dependencies_test.go b/internal/app/validate_dependencies_test.go index afab55da1..743486174 100644 --- a/internal/app/validate_dependencies_test.go +++ b/internal/app/validate_dependencies_test.go @@ -11,234 +11,90 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/execx" - "github.com/sighupio/furyctl/internal/yaml" ) -var ( - valDepWrongKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{ - Ansible: "2.10.0", - Furyagent: "0.0.2", - Kubectl: "1.21.4", - Kustomize: "3.9.8", - Terraform: "0.15.9", +func TestValidateDependencies(t *testing.T) { + testCases := []struct { + desc string + executor execx.Executor + envs map[string]string + kfdConf distribution.Manifest + wantErrCount int + wantErrVal any + wantErrType error + }{ + { + desc: "missing tools and envs", + executor: execx.NewStdExecutor(), + kfdConf: correctKFDConf, + wantErrCount: 8, + wantErrVal: &fs.PathError{}, + wantErrType: app.ErrMissingEnvVar, }, - } - - valDepCorrectKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{ - Ansible: "2.11.2", - Furyagent: "0.0.1", - Kubectl: "1.21.1", - Kustomize: "3.9.4", - Terraform: "0.15.4", + { + desc: "has all tools and envs", + executor: execx.NewFakeExecutor(), + kfdConf: correctKFDConf, + envs: map[string]string{ + "AWS_ACCESS_KEY_ID": "test", + "AWS_SECRET_ACCESS_KEY": "test", + "AWS_DEFAULT_REGION": "test", + }, + wantErrCount: 0, + }, + { + desc: "has wrong tools", + executor: execx.NewFakeExecutor(), + kfdConf: wrongKFDConf, + envs: map[string]string{ + "AWS_ACCESS_KEY_ID": "test", + "AWS_SECRET_ACCESS_KEY": "test", + "AWS_DEFAULT_REGION": "test", + }, + wantErrCount: 5, + wantErrType: app.ErrWrongToolVersion, }, } -) - -func TestValidateDependencies_MissingToolsAndEnvs(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-deps-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(valDepCorrectKFDConf) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - vd := app.NewValidateDependencies(execx.NewStdExecutor()) - - res, err := vd.Execute(app.ValidateDependenciesRequest{ - BinPath: filepath.Join(tmpDir, "bin"), - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - - if len(res.Errors) == 0 { - t.Fatal("Expected validation errors, got none") - } - - if len(res.Errors) != 8 { - t.Fatalf("Expected 8 validation errors, got %d", len(res.Errors)) - } - - for _, err := range res.Errors { - var terr *fs.PathError - - if !errors.As(err, &terr) && !errors.Is(err, app.ErrMissingEnvVar) { - t.Fatalf("Unexpected error: %v", err) - } - } -} - -func TestValidateDependencies_HasAllToolsAndEnvs(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-deps-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(valDepCorrectKFDConf) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - t.Setenv("AWS_ACCESS_KEY_ID", "test") - t.Setenv("AWS_SECRET_ACCESS_KEY", "test") - t.Setenv("AWS_DEFAULT_REGION", "test") - - vd := app.NewValidateDependencies(execx.NewFakeExecutor()) - - res, err := vd.Execute(app.ValidateDependenciesRequest{ - BinPath: filepath.Join(tmpDir, "bin"), - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - - if len(res.Errors) != 0 { - t.Errorf("Did not expect validation errors, got %d", len(res.Errors)) - for _, err := range res.Errors { - t.Log(err) - } - } -} - -func TestValidateDependencies_HasWrongTools(t *testing.T) { - tmpDir := mkDirTemp(t, "furyctl-deps-validation-") - defer rmDirTemp(t, tmpDir) - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(valDepWrongKFDConf) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - t.Setenv("AWS_ACCESS_KEY_ID", "test") - t.Setenv("AWS_SECRET_ACCESS_KEY", "test") - t.Setenv("AWS_DEFAULT_REGION", "test") - - vd := app.NewValidateDependencies(execx.NewFakeExecutor()) - - res, err := vd.Execute(app.ValidateDependenciesRequest{ - BinPath: filepath.Join(tmpDir, "bin"), - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - - if len(res.Errors) != 5 { - t.Fatalf("Expected 5 validation errors, got %d", len(res.Errors)) - } - - for _, err := range res.Errors { - if !errors.Is(err, app.ErrWrongToolVersion) { - t.Errorf("Unexpected error: %v", err) - } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + tmpDir, configFilePath := setupDistroFolder(t, correctFuryctlDefaults, tC.kfdConf) + defer rmDirTemp(t, tmpDir) + + for k, v := range tC.envs { + t.Setenv(k, v) + } + + vd := app.NewValidateDependencies(tC.executor) + + res, err := vd.Execute(app.ValidateDependenciesRequest{ + BinPath: filepath.Join(tmpDir, "bin"), + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if tC.wantErrCount != len(res.Errors) { + t.Errorf("Expected %d validation errors, got %d", tC.wantErrCount, len(res.Errors)) + for _, err := range res.Errors { + t.Log(err) + } + } + + for _, err := range res.Errors { + notErrAs := tC.wantErrVal != nil && !errors.As(err, &tC.wantErrVal) + notErrIs := tC.wantErrType != nil && !errors.Is(err, tC.wantErrType) + + if notErrAs && notErrIs { + t.Fatalf("got error = %v, want = %v", err, tC.wantErrVal) + } + } + }) } } From 7315082315bd0ebf11aac444ae9adbbc47d90fe2 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:07:34 +0200 Subject: [PATCH 041/383] feat: add license banners --- internal/app/common_test.go | 4 ++++ internal/app/validate_config.go | 4 ++++ internal/app/validate_config_test.go | 4 ++++ internal/app/validate_dependencies.go | 4 ++++ internal/app/validate_dependencies_test.go | 4 ++++ internal/execx/exec.go | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/internal/app/common_test.go b/internal/app/common_test.go index 2f9f98ea0..d1e0ace43 100644 --- a/internal/app/common_test.go +++ b/internal/app/common_test.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app_test import ( diff --git a/internal/app/validate_config.go b/internal/app/validate_config.go index fcfa2b600..1690427c1 100644 --- a/internal/app/validate_config.go +++ b/internal/app/validate_config.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app import ( diff --git a/internal/app/validate_config_test.go b/internal/app/validate_config_test.go index 47b568d72..f643ffd1d 100644 --- a/internal/app/validate_config_test.go +++ b/internal/app/validate_config_test.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app_test import ( diff --git a/internal/app/validate_dependencies.go b/internal/app/validate_dependencies.go index dfe2faaac..a64ddce6f 100644 --- a/internal/app/validate_dependencies.go +++ b/internal/app/validate_dependencies.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app import ( diff --git a/internal/app/validate_dependencies_test.go b/internal/app/validate_dependencies_test.go index 743486174..af9916a84 100644 --- a/internal/app/validate_dependencies_test.go +++ b/internal/app/validate_dependencies_test.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app_test import ( diff --git a/internal/execx/exec.go b/internal/execx/exec.go index 12723ed96..049aef847 100644 --- a/internal/execx/exec.go +++ b/internal/execx/exec.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package execx import ( From f079efd7b16c6ad4313050b75105cfee3c073305 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:08:31 +0200 Subject: [PATCH 042/383] feat: rework test targets --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4b476e2f1..d25ee4372 100644 --- a/Makefile +++ b/Makefile @@ -106,11 +106,15 @@ lint: lint-go lint-go: @golangci-lint -v run --color=always --config=${_PROJECT_DIRECTORY}/.rules/.golangci.yml ./... -.PHONY: test +.PHONY: test-unit test-integration -test: +test-unit: @go test -v -covermode=atomic -coverprofile=coverage.out ./... +test-integration: + @bats -t ./test/integration/template-engine/tests.sh + @bats -t ./test/integration/validation-cmd/tests.sh + .PHONY: clean build release clean: deps From 0ef0e04df5f8675e0109a3cc2e095da28be9ac06 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:08:50 +0200 Subject: [PATCH 043/383] feat: optimize ci steps --- .drone.yml | 66 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/.drone.yml b/.drone.yml index 19e00e8f0..cb261b48d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,60 +17,65 @@ steps: - go install github.com/google/addlicense@v1.0.0 - make license-check - - name: lint - image: quay.io/sighup/golang:1.19.0 + - name: build + image: ghcr.io/goreleaser/goreleaser:v1.11.4 pull: always commands: - - make lint + - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 + - git reset --hard + - git fetch --tags + - make build + environment: + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp + CGO_ENABLED: 0 + GITHUB_TOKEN: + from_secret: GITHUB_TOKEN - - name: test + - name: lint image: quay.io/sighup/golang:1.19.0 + depends_on: + - build pull: always commands: - - make test + - make lint environment: - CGO_ENABLED: 0 + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp - - name: build - image: ghcr.io/goreleaser/goreleaser:v1.11.4 - pull: always + - name: test-unit + image: quay.io/sighup/golang:1.19.0 depends_on: - - lint - - test + - build + pull: always + commands: + - make test-unit environment: CGO_ENABLED: 0 - GITHUB_TOKEN: - from_secret: GITHUB_TOKEN - commands: - - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - - git reset --hard - - git fetch --tags - - make build + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp - name: test-integration image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 depends_on: - build + commands: + - make test-integration environment: CGO_ENABLED: 0 FURYCTL_TOKEN: from_secret: FURYCTL_TOKEN - commands: - - bats -t ./test/integration/template-engine/tests.sh - - bats -t ./test/integration/validation-cmd/tests.sh - name: build-release image: ghcr.io/goreleaser/goreleaser:v1.11.4 pull: always depends_on: - lint - - test - - build + - test-unit - test-integration - environment: - CGO_ENABLED: 0 - GITHUB_TOKEN: - from_secret: GITHUB_TOKEN commands: - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard @@ -80,6 +85,13 @@ steps: ref: include: - refs/tags/v** + environment: + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp + CGO_ENABLED: 0 + GITHUB_TOKEN: + from_secret: GITHUB_TOKEN trigger: event: From ba96296a850e1a1cf95ea0a27ee1c9f715436fbf Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:11:49 +0200 Subject: [PATCH 044/383] fix: add missing folder in ci step --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index cb261b48d..31595035c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -21,6 +21,7 @@ steps: image: ghcr.io/goreleaser/goreleaser:v1.11.4 pull: always commands: + - mkdir -p .go/cache .go/modcache .go/tmp - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags From 5cb166581faaee1ad342326d14ed28660ae61c58 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:15:01 +0200 Subject: [PATCH 045/383] fix: remove make from test-integration ci step as the docker image does not include it --- .drone.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 31595035c..59387b23f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -64,7 +64,8 @@ steps: depends_on: - build commands: - - make test-integration + - bats -t ./test/integration/template-engine/tests.sh + - bats -t ./test/integration/validation-cmd/tests.sh environment: CGO_ENABLED: 0 FURYCTL_TOKEN: From 293fb61708c1b48225755890fd5a209ef8947fb7 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:24:00 +0200 Subject: [PATCH 046/383] feat: optimize ci steps --- .drone.yml | 62 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/.drone.yml b/.drone.yml index 59387b23f..d1905efa7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,38 +10,34 @@ node: runner: internal steps: - - name: check + - name: prepare image: quay.io/sighup/golang:1.19.0 + depends_on: + - clone pull: always commands: - - go install github.com/google/addlicense@v1.0.0 - - make license-check + - mkdir -p .go/cache .go/modcache .go/tmp + - go mod download - - name: build - image: ghcr.io/goreleaser/goreleaser:v1.11.4 + - name: license + image: quay.io/sighup/golang:1.19.0 + depends_on: + - clone pull: always commands: - - mkdir -p .go/cache .go/modcache .go/tmp - - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - - git reset --hard - - git fetch --tags - - make build - environment: - GOCACHE: /drone/src/.go/cache - GOMODCACHE: /drone/src/.go/modcache - GOTMPDIR: /drone/src/.go/tmp - CGO_ENABLED: 0 - GITHUB_TOKEN: - from_secret: GITHUB_TOKEN + - go install github.com/google/addlicense@v1.0.0 + - make license-check - name: lint image: quay.io/sighup/golang:1.19.0 depends_on: - - build + - prepare pull: always commands: + - ls -la /drone/src/.go/cache - make lint environment: + CGO_ENABLED: 0 GOCACHE: /drone/src/.go/cache GOMODCACHE: /drone/src/.go/modcache GOTMPDIR: /drone/src/.go/tmp @@ -49,7 +45,7 @@ steps: - name: test-unit image: quay.io/sighup/golang:1.19.0 depends_on: - - build + - prepare pull: always commands: - make test-unit @@ -62,14 +58,34 @@ steps: - name: test-integration image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 depends_on: - - build + - prepare commands: - bats -t ./test/integration/template-engine/tests.sh - bats -t ./test/integration/validation-cmd/tests.sh + + - name: build + image: ghcr.io/goreleaser/goreleaser:v1.11.4 + depends_on: + - lint + - test-unit + - test-integration + pull: always + commands: + - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 + - git reset --hard + - git fetch --tags + - make build + when: + ref: + exclude: + - refs/tags/v** environment: CGO_ENABLED: 0 - FURYCTL_TOKEN: - from_secret: FURYCTL_TOKEN + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp + GITHUB_TOKEN: + from_secret: GITHUB_TOKEN - name: build-release image: ghcr.io/goreleaser/goreleaser:v1.11.4 @@ -88,10 +104,10 @@ steps: include: - refs/tags/v** environment: + CGO_ENABLED: 0 GOCACHE: /drone/src/.go/cache GOMODCACHE: /drone/src/.go/modcache GOTMPDIR: /drone/src/.go/tmp - CGO_ENABLED: 0 GITHUB_TOKEN: from_secret: GITHUB_TOKEN From 505a757fc6bb764934edee7c37bd853dc284be4b Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:34:03 +0200 Subject: [PATCH 047/383] fix: ci test-integration step --- .drone.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index d1905efa7..404d24938 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,6 +27,11 @@ steps: commands: - go install github.com/google/addlicense@v1.0.0 - make license-check + environment: + CGO_ENABLED: 0 + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp - name: lint image: quay.io/sighup/golang:1.19.0 @@ -34,7 +39,6 @@ steps: - prepare pull: always commands: - - ls -la /drone/src/.go/cache - make lint environment: CGO_ENABLED: 0 @@ -56,12 +60,13 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-integration - image: quay.io/sighup/e2e-furyctl:v1.2.1_v0.2.2_v1.20.1_20.04 + image: quay.io/sighup/golang:1.19.0 depends_on: - prepare commands: - - bats -t ./test/integration/template-engine/tests.sh - - bats -t ./test/integration/validation-cmd/tests.sh + - git clone --depth 1 --branch v1.8.0 https://github.com/bats-core/bats-core.git + - ./bats-core/install.sh /usr/local + - make test-integration - name: build image: ghcr.io/goreleaser/goreleaser:v1.11.4 From 03c93a79e4980fb97cec6f5d75ab4713cbd07c03 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:52:22 +0200 Subject: [PATCH 048/383] fix: integration tests --- cmd/validate/config.go | 4 +++- cmd/validate/dependencies.go | 2 +- test/integration/template-engine/tests.sh | 13 ++++++++++--- test/integration/validation-cmd/tests.sh | 23 +++++++++++++++-------- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index fd31c0f2c..fd5eb9dcc 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -14,6 +14,8 @@ import ( "github.com/sighupio/furyctl/internal/cobrax" ) +var ErrValidationFailed = fmt.Errorf("validation failed") + func NewConfigCmd(version string) *cobra.Command { cmd := &cobra.Command{ Use: "config", @@ -40,7 +42,7 @@ func NewConfigCmd(version string) *cobra.Command { fmt.Println(res.Error) - return nil + return ErrValidationFailed } fmt.Println("Config validation succeeded") diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index ba59c29e7..0016712b3 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -50,7 +50,7 @@ func NewDependenciesCmd(version string, executor execx.Executor) *cobra.Command return ErrDependencies } - fmt.Println("Dependencies validation succeeded") + fmt.Println("dependencies validation succeeded") return nil }, diff --git a/test/integration/template-engine/tests.sh b/test/integration/template-engine/tests.sh index 0e68f076b..3acbad119 100644 --- a/test/integration/template-engine/tests.sh +++ b/test/integration/template-engine/tests.sh @@ -3,7 +3,6 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. - load "./../../helper" OS="linux" @@ -15,12 +14,20 @@ CPUARCH="amd64_v1" # CPUARCH="arm64" # fi -FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" +FURYCTL="${PWD}/dist/furyctl" + +@test "prepare" { + info + init(){ + go build -o ${PWD}/dist/furyctl ${PWD}/main.go + } + run init +} @test "furyctl" { info init(){ - "${FURYCTL}" version + ${FURYCTL} version } run init if [[ ${status} -ne 0 ]]; then diff --git a/test/integration/validation-cmd/tests.sh b/test/integration/validation-cmd/tests.sh index bd0e90b1e..824134238 100644 --- a/test/integration/validation-cmd/tests.sh +++ b/test/integration/validation-cmd/tests.sh @@ -3,7 +3,6 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. - load "./../../helper" OS="linux" @@ -15,12 +14,20 @@ CPUARCH="amd64_v1" # CPUARCH="arm64" # fi -FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" +FURYCTL="${PWD}/dist/furyctl" + +@test "prepare" { + info + init(){ + go build -o ${PWD}/dist/furyctl ${PWD}/main.go + } + run init +} @test "furyctl" { info init(){ - "${FURYCTL}" version + ${FURYCTL} version } run init if [[ ${status} -ne 0 ]]; then @@ -40,10 +47,10 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" [ "${status}" -eq 1 ] - if [[ ${output} != *"furyctl.yaml contains validation errors"* ]]; then + if [[ ${output} != *"validation failed"* ]]; then echo "${output}" >&3 fi - [[ "${output}" == *"furyctl.yaml contains validation errors"* ]] + [[ "${output}" == *"validation failed"* ]] } @test "valid furyctl yaml" { @@ -82,7 +89,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" echo "${output}" >&3 fi - [[ "${output}" == *"error while validating system dependencies"* ]] + [[ "${output}" == *"dependencies are not satisfied"* ]] } @test "wrong dependencies installed" { @@ -109,7 +116,7 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" echo "${output}" >&3 fi - [[ "${output}" == *"error while validating system dependencies"* ]] + [[ "${output}" == *"dependencies are not satisfied"* ]] } @test "correct dependencies installed" { @@ -140,5 +147,5 @@ FURYCTL="${PWD}/dist/furyctl_${OS}_${CPUARCH}/furyctl" echo "${output}" >&3 fi - [[ "${output}" == *"All dependencies are satisfied"* ]] + [[ "${output}" == *"dependencies validation succeeded"* ]] } From a3a1956d203ee18ee83a67b058c18925e48aaa1d Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:53:40 +0200 Subject: [PATCH 049/383] feat: add envs to integration tests ci step --- .drone.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.drone.yml b/.drone.yml index 404d24938..4a22e3bca 100644 --- a/.drone.yml +++ b/.drone.yml @@ -67,6 +67,11 @@ steps: - git clone --depth 1 --branch v1.8.0 https://github.com/bats-core/bats-core.git - ./bats-core/install.sh /usr/local - make test-integration + environment: + CGO_ENABLED: 0 + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp - name: build image: ghcr.io/goreleaser/goreleaser:v1.11.4 From 5953e76618610c3032690413b1d7e1de531dc149 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 10:54:56 +0200 Subject: [PATCH 050/383] feat: make build happen in parallel to lints and tests --- .drone.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4a22e3bca..61d02afb4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -76,9 +76,7 @@ steps: - name: build image: ghcr.io/goreleaser/goreleaser:v1.11.4 depends_on: - - lint - - test-unit - - test-integration + - prepare pull: always commands: - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 From c6b9942eb0f9a88a75f28156b60c105585c05d45 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 11:01:54 +0200 Subject: [PATCH 051/383] feat: make ci run on non-internal worker --- .drone.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 61d02afb4..f3d9cb481 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,9 +6,6 @@ kind: pipeline type: docker name: main -node: - runner: internal - steps: - name: prepare image: quay.io/sighup/golang:1.19.0 From 88f569781adb51781cc0e6a202f9e4f297ed99c8 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 18:04:03 +0200 Subject: [PATCH 052/383] feat: add first working draft of the download dependencies command --- cmd/download.go | 22 ++ cmd/download/dependencies.go | 77 +++++++ cmd/root.go | 1 + cmd/validate.go | 6 +- cmd/validate/config.go | 4 +- cmd/validate/dependencies.go | 4 +- internal/app/common_test.go | 66 +----- internal/app/download_dependencies.go | 207 ++++++++++++++++++ internal/app/validate_config.go | 24 +- internal/app/validate_config_test.go | 4 +- internal/app/validate_dependencies.go | 3 +- .../util.go => distribution/download.go} | 39 ++-- .../download_test.go} | 15 +- internal/distribution/model.go | 66 +++--- internal/semver/prefix.go | 17 ++ 15 files changed, 419 insertions(+), 136 deletions(-) create mode 100644 cmd/download.go create mode 100644 cmd/download/dependencies.go create mode 100644 internal/app/download_dependencies.go rename internal/{app/validate/util.go => distribution/download.go} (80%) rename internal/{app/validate/util_test.go => distribution/download_test.go} (89%) create mode 100644 internal/semver/prefix.go diff --git a/cmd/download.go b/cmd/download.go new file mode 100644 index 000000000..4de163551 --- /dev/null +++ b/cmd/download.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/cmd/download" +) + +func NewDownloadCmd(furyctlBinVersion string) *cobra.Command { + dumpCmd := &cobra.Command{ + Use: "download", + Short: "Dowload fury files", + } + + dumpCmd.AddCommand(download.NewDependenciesCmd(furyctlBinVersion)) + + return dumpCmd +} diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go new file mode 100644 index 000000000..5bfd77440 --- /dev/null +++ b/cmd/download/dependencies.go @@ -0,0 +1,77 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package download + +import ( + "errors" + "fmt" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/app" + "github.com/sighupio/furyctl/internal/cobrax" +) + +var ErrDownloadFailed = fmt.Errorf("dependencies download failed") + +func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { + cmd := &cobra.Command{ + Use: "dependencies", + Short: "Download dependencies", + RunE: func(cmd *cobra.Command, _ []string) error { + debug := cobrax.Flag[bool](cmd, "debug").(bool) + furyctlPath := cobrax.Flag[string](cmd, "config").(string) + distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) + + dd := app.NewDownloadDependencies() + + res, err := dd.Execute(app.DownloadDependenciesRequest{ + FuryctlBinVersion: furyctlBinVersion, + DistroLocation: distroLocation, + FuryctlConfPath: furyctlPath, + Debug: debug, + }) + if err != nil { + if !errors.Is(err, app.ErrUnsupportedTools) { + return err + } + + logrus.Warnf("unsupported tools: %v", err) + } + + if res.HasErrors() { + logrus.Debugf("Repository path: %s", res.RepoPath) + + fmt.Println(res.Error) + + return ErrDownloadFailed + } + + fmt.Println("dependencies download succeeded") + + return nil + }, + } + + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Base URL used to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: https://git@github.com/sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index 633d2beed..73a6ecf65 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -78,6 +78,7 @@ Furyctl is a simple CLI tool to: rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.DisableTty, "no-tty", "T", false, "Disable TTY") rootCmd.AddCommand(NewCompletionCmd()) + rootCmd.AddCommand(NewDownloadCmd(versions["version"])) rootCmd.AddCommand(NewDumpCmd()) rootCmd.AddCommand(NewValidateCommand(versions["version"])) rootCmd.AddCommand(NewVersionCmd(versions)) diff --git a/cmd/validate.go b/cmd/validate.go index 418ca1dfd..accc8a7cf 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -11,14 +11,14 @@ import ( "github.com/sighupio/furyctl/internal/execx" ) -func NewValidateCommand(version string) *cobra.Command { +func NewValidateCommand(furyctlBinVersion string) *cobra.Command { validateCmd := &cobra.Command{ Use: "validate", Short: "Validate fury config files and dependencies", } - validateCmd.AddCommand(validate.NewConfigCmd(version)) - validateCmd.AddCommand(validate.NewDependenciesCmd(version, execx.NewStdExecutor())) + validateCmd.AddCommand(validate.NewConfigCmd(furyctlBinVersion)) + validateCmd.AddCommand(validate.NewDependenciesCmd(furyctlBinVersion, execx.NewStdExecutor())) return validateCmd } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index fd5eb9dcc..bd18221b5 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -16,7 +16,7 @@ import ( var ErrValidationFailed = fmt.Errorf("validation failed") -func NewConfigCmd(version string) *cobra.Command { +func NewConfigCmd(furyctlBinVersion string) *cobra.Command { cmd := &cobra.Command{ Use: "config", Short: "Validate furyctl.yaml file", @@ -28,7 +28,7 @@ func NewConfigCmd(version string) *cobra.Command { vc := app.NewValidateConfig() res, err := vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: version, + FuryctlBinVersion: furyctlBinVersion, DistroLocation: distroLocation, FuryctlConfPath: furyctlPath, Debug: debug, diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 0016712b3..15a213b9d 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -17,7 +17,7 @@ import ( var ErrDependencies = fmt.Errorf("dependencies are not satisfied") -func NewDependenciesCmd(version string, executor execx.Executor) *cobra.Command { +func NewDependenciesCmd(furyctlBinVersion string, executor execx.Executor) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", Short: "Validate furyctl.yaml file", @@ -31,7 +31,7 @@ func NewDependenciesCmd(version string, executor execx.Executor) *cobra.Command res, err := vd.Execute(app.ValidateDependenciesRequest{ BinPath: binPath, - FuryctlBinVersion: version, + FuryctlBinVersion: furyctlBinVersion, DistroLocation: distroLocation, FuryctlConfPath: furyctlPath, Debug: debug, diff --git a/internal/app/common_test.go b/internal/app/common_test.go index d1e0ace43..e9065f3f4 100644 --- a/internal/app/common_test.go +++ b/internal/app/common_test.go @@ -46,34 +46,11 @@ var ( } correctKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{ + Version: "v1.24.7", + Modules: distribution.ManifestModules{}, + Kubernetes: distribution.ManifestKubernetes{}, + FuryctlSchemas: distribution.ManifestSchemas{}, + Tools: distribution.ManifestTools{ Ansible: "2.11.2", Furyagent: "0.0.1", Kubectl: "1.21.1", @@ -83,34 +60,11 @@ var ( } wrongKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - }{}, - Kubernetes: struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - }{}, - FuryctlSchemas: struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - }{}, - Tools: struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - }{ + Version: "v1.24.7", + Modules: distribution.ManifestModules{}, + Kubernetes: distribution.ManifestKubernetes{}, + FuryctlSchemas: distribution.ManifestSchemas{}, + Tools: distribution.ManifestTools{ Ansible: "2.10.0", Furyagent: "0.0.2", Kubectl: "1.21.4", diff --git a/internal/app/download_dependencies.go b/internal/app/download_dependencies.go new file mode 100644 index 000000000..0581ac570 --- /dev/null +++ b/internal/app/download_dependencies.go @@ -0,0 +1,207 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package app + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" +) + +var ( + ErrDownloadingModule = errors.New("error downloading module") + ErrUnsupportedTools = errors.New("unsupported tools") +) + +type DownloadDependenciesRequest struct { + FuryctlBinVersion string + DistroLocation string + FuryctlConfPath string + Debug bool +} + +type DownloadDependenciesResponse struct { + Error error + RepoPath string +} + +func (v DownloadDependenciesResponse) HasErrors() bool { + return v.Error != nil +} + +func NewDownloadDependencies() *DownloadDependencies { + return &DownloadDependencies{} +} + +type DownloadDependencies struct{} + +func (dd *DownloadDependencies) Execute(req DownloadDependenciesRequest) (DownloadDependenciesResponse, error) { + dres, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + if err != nil { + return DownloadDependenciesResponse{}, err + } + + if err := dd.DownloadModules(dres.DistroManifest.Modules); err != nil { + return DownloadDependenciesResponse{}, err + } + if err := dd.DownloadInstallers(dres.DistroManifest.Kubernetes); err != nil { + return DownloadDependenciesResponse{}, err + } + if err := dd.DownloadTools(dres.DistroManifest.Tools); err != nil { + return DownloadDependenciesResponse{}, err + } + + return DownloadDependenciesResponse{ + Error: nil, + RepoPath: dres.RepoPath, + }, nil +} + +func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestModules) error { + defaultPrefix := "https://github.com/sighupio/kubernetes-fury" + fallbackPrefix := "https://github.com/sighupio/fury-kubernetes" + + basePath, err := os.Getwd() + if err != nil { + return err + } + + mods := reflect.ValueOf(modules) + + for i := 0; i < mods.NumField(); i++ { + name := strings.ToLower(mods.Type().Field(i).Name) + version := mods.Field(i).Interface().(string) + + errors := []error{} + for _, prefix := range []string{defaultPrefix, fallbackPrefix} { + src := fmt.Sprintf("git::%s-%s.git?ref=%s", prefix, name, version) + + if err := distribution.ClientGet(src, filepath.Join(basePath, "vendor", "modules", name)); err != nil { + errors = append(errors, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err)) + continue + } + + errors = []error{} + break + } + + if len(errors) > 0 { + return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, errors) + } + } + + return nil +} + +func (dd *DownloadDependencies) DownloadInstallers(installers distribution.ManifestKubernetes) error { + basePath, err := os.Getwd() + if err != nil { + return err + } + + insts := reflect.ValueOf(installers) + + for i := 0; i < insts.NumField(); i++ { + name := strings.ToLower(insts.Type().Field(i).Name) + version := insts.Field(i).Interface().(distribution.ManifestProvider).Installer + + src := fmt.Sprintf("git::https://github.com/sighupio/fury-%s-installer?ref=%s", name, version) + + if err := distribution.ClientGet(src, filepath.Join(basePath, "vendor", "installers", name)); err != nil { + return err + } + } + + return nil +} + +func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) error { + basePath, err := os.Getwd() + if err != nil { + return err + } + + tls := reflect.ValueOf(tools) + + unsupportedTools := []string{} + for i := 0; i < tls.NumField(); i++ { + name := strings.ToLower(tls.Type().Field(i).Name) + version := tls.Field(i).Interface().(string) + + src, dstName := dd.fmtPaths(name, version, runtime.GOOS, "amd64") + if src == "" { + unsupportedTools = append(unsupportedTools, name) + continue + } + + dst := filepath.Join(basePath, "vendor", "bin") + + if err := distribution.ClientGet(src, dst); err != nil { + return err + } + + if dstName != name { + if err := os.Rename(filepath.Join(dst, dstName), filepath.Join(dst, name)); err != nil { + return err + } + } + } + + if unsupportedTools != nil { + return fmt.Errorf("%w: %v", ErrUnsupportedTools, unsupportedTools) + } + + return nil +} + +// fmtPaths returns the final location of the tool to download and the name of the downloaded binary +func (dd *DownloadDependencies) fmtPaths(toolName, toolVersion, osName, archName string) (string, string) { + if toolName == "furyagent" { + return fmt.Sprintf( + "https://github.com/sighupio/furyagent/releases/download/%s/furyagent-%s-%s", + semver.EnsurePrefix(toolVersion, "v"), + osName, + archName, + ), fmt.Sprintf("furyagent-%s-%s", osName, archName) + } + + if toolName == "kubectl" { + return fmt.Sprintf( + "https://dl.k8s.io/release/%s/bin/%s/%s/kubectl", + semver.EnsurePrefix(toolVersion, "v"), + osName, + archName, + ), "kubectl" + } + + if toolName == "kustomize" { + return fmt.Sprintf( + "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/%s/kustomize_%s_%s_%s.tar.gz", + semver.EnsurePrefix(toolVersion, "v"), + semver.EnsurePrefix(toolVersion, "v"), + osName, + archName, + ), "kustomize" + } + + if toolName == "terraform" { + return fmt.Sprintf( + "https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_%s.zip", + semver.EnsureNoPrefix(toolVersion, "v"), + semver.EnsureNoPrefix(toolVersion, "v"), + osName, + archName, + ), "terraform" + } + + return "", "" +} diff --git a/internal/app/validate_config.go b/internal/app/validate_config.go index 1690427c1..63b2e479c 100644 --- a/internal/app/validate_config.go +++ b/internal/app/validate_config.go @@ -9,7 +9,7 @@ import ( "os" "path/filepath" - "github.com/sighupio/furyctl/internal/app/validate" + "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/schema/santhosh" "github.com/sighupio/furyctl/internal/yaml" @@ -38,24 +38,24 @@ func NewValidateConfig() *ValidateConfig { type ValidateConfig struct{} func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigResponse, error) { - res, err := validate.DownloadDistro(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + res, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) if err != nil { return ValidateConfigResponse{}, err } - schemaPath, err := validate.GetSchemaPath(res.RepoPath, res.MinimalConf) + schemaPath, err := distribution.GetSchemaPath(res.RepoPath, res.MinimalConf) if err != nil { return ValidateConfigResponse{}, err } - defaultPath := validate.GetDefaultPath(res.RepoPath) + defaultPath := distribution.GetDefaultPath(res.RepoPath) defaultedFuryctlConfPath, err := h.mergeConfigAndDefaults(req.FuryctlConfPath, defaultPath) if err != nil { return ValidateConfigResponse{}, err } if !req.Debug { - defer validate.CleanupTempDir(filepath.Base(defaultedFuryctlConfPath)) + defer distribution.CleanupTempDir(filepath.Base(defaultedFuryctlConfPath)) } schema, err := santhosh.LoadSchema(schemaPath) @@ -81,12 +81,12 @@ func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigRespo func (h *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) if err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrYamlUnmarshalFile, err) + return "", fmt.Errorf("%w: %v", distribution.ErrYamlUnmarshalFile, err) } furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) if err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrYamlUnmarshalFile, err) + return "", fmt.Errorf("%w: %v", distribution.ErrYamlUnmarshalFile, err) } defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") @@ -96,7 +96,7 @@ func (h *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePat defaultedDistribution, err := distroMerger.Merge() if err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrMergeDistroConfig, err) + return "", fmt.Errorf("%w: %v", distribution.ErrMergeDistroConfig, err) } furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") @@ -106,22 +106,22 @@ func (h *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePat defaultedFuryctl, err := furyctlMerger.Merge() if err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrMergeCompleteConfig, err) + return "", fmt.Errorf("%w: %v", distribution.ErrMergeCompleteConfig, err) } outYaml, err := yaml.MarshalV2(defaultedFuryctl) if err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrYamlMarshalFile, err) + return "", fmt.Errorf("%w: %v", distribution.ErrYamlMarshalFile, err) } outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") if err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrCreatingTempDir, err) + return "", fmt.Errorf("%w: %v", distribution.ErrCreatingTempDir, err) } confPath := filepath.Join(outDirPath, "config.yaml") if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - return "", fmt.Errorf("%w: %v", validate.ErrWriteFile, err) + return "", fmt.Errorf("%w: %v", distribution.ErrWriteFile, err) } return confPath, nil diff --git a/internal/app/validate_config_test.go b/internal/app/validate_config_test.go index f643ffd1d..da28e853d 100644 --- a/internal/app/validate_config_test.go +++ b/internal/app/validate_config_test.go @@ -14,7 +14,7 @@ import ( "github.com/santhosh-tekuri/jsonschema" "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/app/validate" + "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/yaml" ) @@ -119,7 +119,7 @@ func TestValidateConfig(t *testing.T) { rmDirTemp(t, tmpDir) }, wantErr: true, - wantErrType: validate.ErrDownloadingFolder, + wantErrType: distribution.ErrDownloadingFolder, }, { desc: "success", diff --git a/internal/app/validate_dependencies.go b/internal/app/validate_dependencies.go index a64ddce6f..d4ff0a424 100644 --- a/internal/app/validate_dependencies.go +++ b/internal/app/validate_dependencies.go @@ -12,7 +12,6 @@ import ( "regexp" "strings" - "github.com/sighupio/furyctl/internal/app/validate" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/execx" ) @@ -55,7 +54,7 @@ type ValidateDependencies struct { func (vd *ValidateDependencies) Execute(req ValidateDependenciesRequest) (ValidateDependenciesResponse, error) { res := ValidateDependenciesResponse{} - dres, err := validate.DownloadDistro(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + dres, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) if err != nil { return res, err } diff --git a/internal/app/validate/util.go b/internal/distribution/download.go similarity index 80% rename from internal/app/validate/util.go rename to internal/distribution/download.go index 4d16a302d..8bb6f1ad0 100644 --- a/internal/app/validate/util.go +++ b/internal/distribution/download.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package validate +package distribution import ( "errors" @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/go-getter" "github.com/sirupsen/logrus" - "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/yaml" ) @@ -37,42 +36,40 @@ var ( type downloadResult struct { RepoPath string - MinimalConf distribution.FuryctlConfig - DistroManifest distribution.Manifest + MinimalConf FuryctlConfig + DistroManifest Manifest } -func DownloadDistro( - version string, +func Download( + furyctlBinVersion string, distroLocation string, furyctlConfPath string, debug bool, ) (downloadResult, error) { - minimalConf, err := yaml.FromFileV3[distribution.FuryctlConfig](furyctlConfPath) + minimalConf, err := yaml.FromFileV3[FuryctlConfig](furyctlConfPath) if err != nil { return downloadResult{}, err } - furyctlConfVersion := minimalConf.Spec.DistributionVersion + furyctlConfSemVer := minimalConf.Spec.DistributionVersion - if version != "unknown" { - furyctlBinVersion, err := semver.NewVersion(fmt.Sprintf("v%s", version)) + if furyctlBinVersion != "unknown" { + furyctlBinSemVer, err := semver.NewVersion(fmt.Sprintf("v%s", furyctlBinVersion)) if err != nil { return downloadResult{}, err } - sameMinors := semver.SameMinor(furyctlConfVersion, furyctlBinVersion) - - if !sameMinors { + if !semver.SameMinor(furyctlConfSemVer, furyctlBinSemVer) { logrus.Warnf( "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", furyctlBinVersion, - furyctlConfVersion, + furyctlConfSemVer, ) } } if distroLocation == "" { - distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfVersion.String()) + distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfSemVer.String()) } repoPath, err := DownloadDirectory(distroLocation) @@ -84,15 +81,15 @@ func DownloadDistro( } kfdPath := filepath.Join(repoPath, "kfd.yaml") - kfdManifest, err := yaml.FromFileV3[distribution.Manifest](kfdPath) + kfdManifest, err := yaml.FromFileV3[Manifest](kfdPath) if err != nil { return downloadResult{}, err } - if !semver.SamePatch(furyctlConfVersion, kfdManifest.Version) { + if !semver.SamePatch(furyctlConfSemVer, kfdManifest.Version) { return downloadResult{}, fmt.Errorf( - "minor versions mismatch: furyctl.yaml has %s, but furyctl has %s", - furyctlConfVersion.String(), + "versions mismatch: furyctl.yaml = '%s', furyctl binary = '%s'", + furyctlConfSemVer.String(), kfdManifest.Version.String(), ) } @@ -104,7 +101,7 @@ func DownloadDistro( }, nil } -func GetSchemaPath(basePath string, conf distribution.FuryctlConfig) (string, error) { +func GetSchemaPath(basePath string, conf FuryctlConfig) (string, error) { avp := strings.Split(conf.ApiVersion, "/") if len(avp) < 2 { @@ -167,7 +164,7 @@ func ClientGet(src, dst string) error { client := &getter.Client{ Src: fullSrc, Dst: dst, - Mode: getter.ClientModeDir, + Mode: getter.ClientModeAny, } err := client.Get() diff --git a/internal/app/validate/util_test.go b/internal/distribution/download_test.go similarity index 89% rename from internal/app/validate/util_test.go rename to internal/distribution/download_test.go index 42efd3490..a5fcfb6d1 100644 --- a/internal/app/validate/util_test.go +++ b/internal/distribution/download_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package validate_test +package distribution_test import ( "fmt" @@ -10,7 +10,6 @@ import ( "path/filepath" "testing" - "github.com/sighupio/furyctl/internal/app/validate" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" ) @@ -82,17 +81,17 @@ func TestGetSchemaPath(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := validate.GetSchemaPath(tt.basePath, tt.conf) + got, err := distribution.GetSchemaPath(tt.basePath, tt.conf) if err != nil { if err.Error() != tt.wantErr.Error() { - t.Errorf("validate.GetSchemaPath() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("distribution.GetSchemaPath() error = %v, wantErr %v", err, tt.wantErr) } return } if got != tt.want { - t.Errorf("validate.GetSchemaPath() = %v, want %v", got, tt.want) + t.Errorf("distribution.GetSchemaPath() = %v, want %v", got, tt.want) } }) } @@ -114,7 +113,7 @@ func TestDownloadDirectory(t *testing.T) { _ = os.RemoveAll(tmpDir) }() - dlDir, err := validate.DownloadDirectory(tmpDir) + dlDir, err := distribution.DownloadDirectory(tmpDir) if err != nil { t.Fatalf("error downloading directory: %v", err) } @@ -148,7 +147,7 @@ func TestClientGet(t *testing.T) { _ = os.RemoveAll(tmpDir) }() - err = validate.ClientGet(in, out) + err = distribution.ClientGet(in, out) if err != nil { t.Fatalf("error getting directory: %v", err) } @@ -178,7 +177,7 @@ func TestUrlHasForcedProtocol(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := validate.UrlHasForcedProtocol(tt.url); got != tt.want { + if got := distribution.UrlHasForcedProtocol(tt.url); got != tt.want { t.Errorf("urlHasForcedProtocol() = %v, want %v", got, tt.want) } }) diff --git a/internal/distribution/model.go b/internal/distribution/model.go index e67ed2fea..98c96246b 100644 --- a/internal/distribution/model.go +++ b/internal/distribution/model.go @@ -42,35 +42,45 @@ func (c *FuryctlConfig) UnmarshalYAML(unmarshal func(any) error) error { return nil } +type ManifestModules struct { + Auth string `yaml:"auth"` + Dr string `yaml:"dr"` + Ingress string `yaml:"ingress"` + Logging string `yaml:"logging"` + Monitoring string `yaml:"monitoring"` + Opa string `yaml:"opa"` +} + +type ManifestProvider struct { + Version string `yaml:"version"` + Installer string `yaml:"installer"` +} + +type ManifestKubernetes struct { + Eks ManifestProvider `yaml:"eks"` +} + +type ManifestSchemas struct { + Eks []struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + } `yaml:"eks"` +} + +type ManifestTools struct { + Ansible string `yaml:"ansible"` + Furyagent string `yaml:"furyagent"` + Kubectl string `yaml:"kubectl"` + Kustomize string `yaml:"kustomize"` + Terraform string `yaml:"terraform"` +} + type Manifest struct { - Version semver.Version `yaml:"version"` - Modules struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` - } `yaml:"modules"` - Kubernetes struct { - Eks struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` - } `yaml:"eks"` - } `yaml:"kubernets"` - FuryctlSchemas struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` - } `yaml:"furyctlSchemas"` - Tools struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` - } `yaml:"tools"` + Version semver.Version `yaml:"version"` + Modules ManifestModules `yaml:"modules"` + Kubernetes ManifestKubernetes `yaml:"kubernetes"` + FuryctlSchemas ManifestSchemas `yaml:"furyctlSchemas"` + Tools ManifestTools `yaml:"tools"` } func (m *Manifest) UnmarshalYAML(unmarshal func(any) error) error { diff --git a/internal/semver/prefix.go b/internal/semver/prefix.go new file mode 100644 index 000000000..680902eb8 --- /dev/null +++ b/internal/semver/prefix.go @@ -0,0 +1,17 @@ +package semver + +import "strings" + +func EnsurePrefix(version, prefix string) string { + if !strings.HasPrefix(version, prefix) { + return prefix + version + } + return version +} + +func EnsureNoPrefix(version, prefix string) string { + if strings.HasPrefix(version, prefix) { + return strings.TrimPrefix(version, prefix) + } + return version +} From c857d9740aa826d6e5316fc354a8537828ba92bd Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 18:30:26 +0200 Subject: [PATCH 053/383] chore: refactor go-getter client --- cmd/download/dependencies.go | 3 +- internal/app/download_dependencies.go | 19 ++++--- internal/distribution/download.go | 48 +---------------- internal/distribution/download_test.go | 60 --------------------- internal/netx/client.go | 9 ++++ internal/netx/hashicorp.go | 65 +++++++++++++++++++++++ internal/netx/hashicorp_test.go | 73 ++++++++++++++++++++++++++ internal/semver/prefix.go | 4 ++ 8 files changed, 167 insertions(+), 114 deletions(-) create mode 100644 internal/netx/client.go create mode 100644 internal/netx/hashicorp.go create mode 100644 internal/netx/hashicorp_test.go diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 5bfd77440..a73257161 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -13,6 +13,7 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" + "github.com/sighupio/furyctl/internal/netx" ) var ErrDownloadFailed = fmt.Errorf("dependencies download failed") @@ -26,7 +27,7 @@ func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - dd := app.NewDownloadDependencies() + dd := app.NewDownloadDependencies(netx.NewGoGetterClient()) res, err := dd.Execute(app.DownloadDependenciesRequest{ FuryctlBinVersion: furyctlBinVersion, diff --git a/internal/app/download_dependencies.go b/internal/app/download_dependencies.go index 0581ac570..ee82c7c90 100644 --- a/internal/app/download_dependencies.go +++ b/internal/app/download_dependencies.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/netx" "github.com/sighupio/furyctl/internal/semver" ) @@ -38,11 +39,15 @@ func (v DownloadDependenciesResponse) HasErrors() bool { return v.Error != nil } -func NewDownloadDependencies() *DownloadDependencies { - return &DownloadDependencies{} +func NewDownloadDependencies(client netx.Client) *DownloadDependencies { + return &DownloadDependencies{ + client: client, + } } -type DownloadDependencies struct{} +type DownloadDependencies struct { + client netx.Client +} func (dd *DownloadDependencies) Execute(req DownloadDependenciesRequest) (DownloadDependenciesResponse, error) { dres, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) @@ -85,7 +90,7 @@ func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestMod for _, prefix := range []string{defaultPrefix, fallbackPrefix} { src := fmt.Sprintf("git::%s-%s.git?ref=%s", prefix, name, version) - if err := distribution.ClientGet(src, filepath.Join(basePath, "vendor", "modules", name)); err != nil { + if err := dd.client.Download(src, filepath.Join(basePath, "vendor", "modules", name)); err != nil { errors = append(errors, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err)) continue } @@ -116,7 +121,7 @@ func (dd *DownloadDependencies) DownloadInstallers(installers distribution.Manif src := fmt.Sprintf("git::https://github.com/sighupio/fury-%s-installer?ref=%s", name, version) - if err := distribution.ClientGet(src, filepath.Join(basePath, "vendor", "installers", name)); err != nil { + if err := dd.client.Download(src, filepath.Join(basePath, "vendor", "installers", name)); err != nil { return err } } @@ -143,9 +148,9 @@ func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) continue } - dst := filepath.Join(basePath, "vendor", "bin") + dst := filepath.Join(basePath, "vendor", "bin", name) - if err := distribution.ClientGet(src, dst); err != nil { + if err := dd.client.Download(src, dst); err != nil { return err } diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 8bb6f1ad0..8c35672a7 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -11,9 +11,9 @@ import ( "path/filepath" "strings" - "github.com/hashicorp/go-getter" "github.com/sirupsen/logrus" + "github.com/sighupio/furyctl/internal/netx" "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/yaml" ) @@ -21,10 +21,6 @@ import ( const DefaultBaseUrl = "https://git@github.com/sighupio/fury-distribution?ref=%s" var ( - downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} - - errDownloadOptionsExausted = errors.New("downloading options exausted") - ErrCreatingTempDir = errors.New("error creating temp dir") ErrDownloadingFolder = errors.New("error downloading folder") ErrMergeCompleteConfig = errors.New("error merging complete config") @@ -134,7 +130,7 @@ func DownloadDirectory(src string) (string, error) { logrus.Debugf("Downloading '%s' in '%s'", src, dst) - if err := ClientGet(src, dst); err != nil { + if err := netx.NewGoGetterClient().Download(src, dst); err != nil { return "", fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } @@ -148,43 +144,3 @@ func CleanupTempDir(dir string) { } } } - -// ClientGet tries a few different protocols to get the source file or directory. -func ClientGet(src, dst string) error { - protocols := []string{""} - if !UrlHasForcedProtocol(src) { - protocols = downloadProtocols - } - - for _, protocol := range protocols { - fullSrc := fmt.Sprintf("%s%s", protocol, src) - - logrus.Debugf("Trying to download from: %s", fullSrc) - - client := &getter.Client{ - Src: fullSrc, - Dst: dst, - Mode: getter.ClientModeAny, - } - - err := client.Get() - if err == nil { - return nil - } - - logrus.Debug(err) - } - - return errDownloadOptionsExausted -} - -// UrlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. -func UrlHasForcedProtocol(url string) bool { - for _, dp := range downloadProtocols { - if dp != "" && strings.HasPrefix(url, dp) { - return true - } - } - - return false -} diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index a5fcfb6d1..7143215b5 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -123,63 +123,3 @@ func TestDownloadDirectory(t *testing.T) { t.Fatalf("error checking downloaded file: %v", err) } } - -func TestClientGet(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-clientget-test-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - in := filepath.Join(tmpDir, "in") - out := filepath.Join(tmpDir, "out") - - if err := os.MkdirAll(in, 0o755); err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - src, err := os.Create(filepath.Join(in, "test.txt")) - if err != nil { - t.Fatalf("error creating temp input file: %v", err) - } - - defer func() { - src.Close() - _ = os.RemoveAll(tmpDir) - }() - - err = distribution.ClientGet(in, out) - if err != nil { - t.Fatalf("error getting directory: %v", err) - } - - _, err = os.Stat(filepath.Join(out, "test.txt")) - if err != nil { - t.Fatalf("error getting file: %v", err) - } -} - -func TestUrlHasForcedProtocol(t *testing.T) { - tests := []struct { - name string - url string - want bool - }{ - { - name: "test with http", - url: "http::test.com", - want: true, - }, - { - name: "test without protocol", - url: "test.com", - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := distribution.UrlHasForcedProtocol(tt.url); got != tt.want { - t.Errorf("urlHasForcedProtocol() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/netx/client.go b/internal/netx/client.go new file mode 100644 index 000000000..c1c7c87b3 --- /dev/null +++ b/internal/netx/client.go @@ -0,0 +1,9 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package netx + +type Client interface { + Download(src, dst string) error +} diff --git a/internal/netx/hashicorp.go b/internal/netx/hashicorp.go new file mode 100644 index 000000000..fb5e421b7 --- /dev/null +++ b/internal/netx/hashicorp.go @@ -0,0 +1,65 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package netx + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/go-getter" + "github.com/sirupsen/logrus" +) + +var ( + downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} + + errDownloadOptionsExausted = errors.New("downloading options exausted") +) + +func NewGoGetterClient() *GoGetterClient { + return &GoGetterClient{} +} + +type GoGetterClient struct{} + +func (g *GoGetterClient) Download(src, dst string) error { + protocols := []string{""} + if !UrlHasForcedProtocol(src) { + protocols = downloadProtocols + } + + for _, protocol := range protocols { + fullSrc := fmt.Sprintf("%s%s", protocol, src) + + logrus.Debugf("Trying to download from: %s", fullSrc) + + client := &getter.Client{ + Src: fullSrc, + Dst: dst, + Mode: getter.ClientModeAny, + } + + err := client.Get() + if err == nil { + return nil + } + + logrus.Debug(err) + } + + return errDownloadOptionsExausted +} + +// UrlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. +func UrlHasForcedProtocol(url string) bool { + for _, dp := range downloadProtocols { + if dp != "" && strings.HasPrefix(url, dp) { + return true + } + } + + return false +} diff --git a/internal/netx/hashicorp_test.go b/internal/netx/hashicorp_test.go new file mode 100644 index 000000000..a7bda1a00 --- /dev/null +++ b/internal/netx/hashicorp_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package netx_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/sighupio/furyctl/internal/netx" +) + +func Test_GoGetterClient_Download(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-clientget-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + in := filepath.Join(tmpDir, "in") + out := filepath.Join(tmpDir, "out") + + if err := os.MkdirAll(in, 0o755); err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + src, err := os.Create(filepath.Join(in, "test.txt")) + if err != nil { + t.Fatalf("error creating temp input file: %v", err) + } + + defer func() { + src.Close() + _ = os.RemoveAll(tmpDir) + }() + + err = netx.NewGoGetterClient().Download(in, out) + if err != nil { + t.Fatalf("error getting directory: %v", err) + } + + _, err = os.Stat(filepath.Join(out, "test.txt")) + if err != nil { + t.Fatalf("error getting file: %v", err) + } +} + +func TestUrlHasForcedProtocol(t *testing.T) { + tests := []struct { + name string + url string + want bool + }{ + { + name: "test with http", + url: "http::test.com", + want: true, + }, + { + name: "test without protocol", + url: "test.com", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := netx.UrlHasForcedProtocol(tt.url); got != tt.want { + t.Errorf("urlHasForcedProtocol() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/semver/prefix.go b/internal/semver/prefix.go index 680902eb8..af8c22fbd 100644 --- a/internal/semver/prefix.go +++ b/internal/semver/prefix.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package semver import "strings" From 0e19d51319e251c92302740abc2daafb546f42d4 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 22 Sep 2022 18:41:47 +0200 Subject: [PATCH 054/383] chore: refactor some generic functions into their own modules --- internal/app/validate_config.go | 3 +- internal/distribution/download.go | 46 +++++++++----------------- internal/distribution/download_test.go | 28 ---------------- internal/osx/path.go | 16 +++++++++ 4 files changed, 34 insertions(+), 59 deletions(-) create mode 100644 internal/osx/path.go diff --git a/internal/app/validate_config.go b/internal/app/validate_config.go index 63b2e479c..6e2bd4a9a 100644 --- a/internal/app/validate_config.go +++ b/internal/app/validate_config.go @@ -11,6 +11,7 @@ import ( "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/osx" "github.com/sighupio/furyctl/internal/schema/santhosh" "github.com/sighupio/furyctl/internal/yaml" ) @@ -55,7 +56,7 @@ func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigRespo return ValidateConfigResponse{}, err } if !req.Debug { - defer distribution.CleanupTempDir(filepath.Base(defaultedFuryctlConfPath)) + defer osx.CleanupTempDir(filepath.Base(defaultedFuryctlConfPath)) } schema, err := santhosh.LoadSchema(schemaPath) diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 8c35672a7..473c11736 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -14,6 +14,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/furyctl/internal/netx" + "github.com/sighupio/furyctl/internal/osx" "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/yaml" ) @@ -68,15 +69,25 @@ func Download( distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfSemVer.String()) } - repoPath, err := DownloadDirectory(distroLocation) + baseDst, err := os.MkdirTemp("", "furyctl-") if err != nil { - return downloadResult{}, err + return downloadResult{}, fmt.Errorf("%w: %v", ErrCreatingTempDir, err) + } + src := distroLocation + dst := filepath.Join(baseDst, "data") + + logrus.Debugf("Downloading '%s' in '%s'", src, dst) + + if err := netx.NewGoGetterClient().Download(src, dst); err != nil { + return downloadResult{}, fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } + if !debug { - defer CleanupTempDir(filepath.Base(repoPath)) + defer osx.CleanupTempDir(filepath.Base(dst)) + } - kfdPath := filepath.Join(repoPath, "kfd.yaml") + kfdPath := filepath.Join(dst, "kfd.yaml") kfdManifest, err := yaml.FromFileV3[Manifest](kfdPath) if err != nil { return downloadResult{}, err @@ -91,7 +102,7 @@ func Download( } return downloadResult{ - RepoPath: repoPath, + RepoPath: dst, MinimalConf: minimalConf, DistroManifest: kfdManifest, }, nil @@ -119,28 +130,3 @@ func GetSchemaPath(basePath string, conf FuryctlConfig) (string, error) { func GetDefaultPath(basePath string) string { return filepath.Join(basePath, "furyctl-defaults.yaml") } - -func DownloadDirectory(src string) (string, error) { - baseDst, err := os.MkdirTemp("", "furyctl-") - if err != nil { - return "", fmt.Errorf("%w: %v", ErrCreatingTempDir, err) - } - - dst := filepath.Join(baseDst, "data") - - logrus.Debugf("Downloading '%s' in '%s'", src, dst) - - if err := netx.NewGoGetterClient().Download(src, dst); err != nil { - return "", fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) - } - - return dst, nil -} - -func CleanupTempDir(dir string) { - if err := os.RemoveAll(dir); err != nil { - if !errors.Is(err, os.ErrNotExist) { - logrus.Error(err) - } - } -} diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index 7143215b5..16afb06ff 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -6,7 +6,6 @@ package distribution_test import ( "fmt" - "os" "path/filepath" "testing" @@ -96,30 +95,3 @@ func TestGetSchemaPath(t *testing.T) { }) } } - -func TestDownloadDirectory(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-download-test-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - src, err := os.Create(filepath.Join(tmpDir, "test.txt")) - if err != nil { - t.Fatalf("error creating temp input file: %v", err) - } - - defer func() { - src.Close() - _ = os.RemoveAll(tmpDir) - }() - - dlDir, err := distribution.DownloadDirectory(tmpDir) - if err != nil { - t.Fatalf("error downloading directory: %v", err) - } - - _, err = os.Stat(filepath.Join(dlDir, "test.txt")) - if err != nil { - t.Fatalf("error checking downloaded file: %v", err) - } -} diff --git a/internal/osx/path.go b/internal/osx/path.go new file mode 100644 index 000000000..6d5b3a272 --- /dev/null +++ b/internal/osx/path.go @@ -0,0 +1,16 @@ +package osx + +import ( + "errors" + "os" +) + +func CleanupTempDir(dir string) error { + if err := os.RemoveAll(dir); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + } + + return nil +} From b11d0e7b7972a7e16ff39644df68ec5a7c91b195 Mon Sep 17 00:00:00 2001 From: omissis Date: Fri, 23 Sep 2022 11:36:59 +0200 Subject: [PATCH 055/383] chore: refactor tools-related code used by download dependencies command --- cmd/download/dependencies.go | 5 +- internal/app/download_dependencies.go | 95 ++++++--------------------- internal/distribution/download.go | 1 - internal/tools/furyagent.go | 40 +++++++++++ internal/tools/kubectl.go | 35 ++++++++++ internal/tools/kustomize.go | 36 ++++++++++ internal/tools/terraform.go | 36 ++++++++++ internal/tools/tool.go | 28 ++++++++ 8 files changed, 200 insertions(+), 76 deletions(-) create mode 100644 internal/tools/furyagent.go create mode 100644 internal/tools/kubectl.go create mode 100644 internal/tools/kustomize.go create mode 100644 internal/tools/terraform.go create mode 100644 internal/tools/tool.go diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index a73257161..3386b8633 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -27,7 +27,10 @@ func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - dd := app.NewDownloadDependencies(netx.NewGoGetterClient()) + dd, err := app.NewDownloadDependencies(netx.NewGoGetterClient()) + if err != nil { + return err + } res, err := dd.Execute(app.DownloadDependenciesRequest{ FuryctlBinVersion: furyctlBinVersion, diff --git a/internal/app/download_dependencies.go b/internal/app/download_dependencies.go index ee82c7c90..a603343c4 100644 --- a/internal/app/download_dependencies.go +++ b/internal/app/download_dependencies.go @@ -10,12 +10,11 @@ import ( "os" "path/filepath" "reflect" - "runtime" "strings" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/netx" - "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/tools" ) var ( @@ -39,14 +38,22 @@ func (v DownloadDependenciesResponse) HasErrors() bool { return v.Error != nil } -func NewDownloadDependencies(client netx.Client) *DownloadDependencies { - return &DownloadDependencies{ - client: client, +func NewDownloadDependencies(client netx.Client) (*DownloadDependencies, error) { + basePath, err := os.Getwd() + if err != nil { + return nil, err } + + return &DownloadDependencies{ + client: client, + basePath: basePath, + }, nil } type DownloadDependencies struct { - client netx.Client + client netx.Client + toolFactory tools.Factory + basePath string } func (dd *DownloadDependencies) Execute(req DownloadDependenciesRequest) (DownloadDependenciesResponse, error) { @@ -75,11 +82,6 @@ func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestMod defaultPrefix := "https://github.com/sighupio/kubernetes-fury" fallbackPrefix := "https://github.com/sighupio/fury-kubernetes" - basePath, err := os.Getwd() - if err != nil { - return err - } - mods := reflect.ValueOf(modules) for i := 0; i < mods.NumField(); i++ { @@ -90,7 +92,7 @@ func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestMod for _, prefix := range []string{defaultPrefix, fallbackPrefix} { src := fmt.Sprintf("git::%s-%s.git?ref=%s", prefix, name, version) - if err := dd.client.Download(src, filepath.Join(basePath, "vendor", "modules", name)); err != nil { + if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "modules", name)); err != nil { errors = append(errors, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err)) continue } @@ -108,11 +110,6 @@ func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestMod } func (dd *DownloadDependencies) DownloadInstallers(installers distribution.ManifestKubernetes) error { - basePath, err := os.Getwd() - if err != nil { - return err - } - insts := reflect.ValueOf(installers) for i := 0; i < insts.NumField(); i++ { @@ -121,7 +118,7 @@ func (dd *DownloadDependencies) DownloadInstallers(installers distribution.Manif src := fmt.Sprintf("git::https://github.com/sighupio/fury-%s-installer?ref=%s", name, version) - if err := dd.client.Download(src, filepath.Join(basePath, "vendor", "installers", name)); err != nil { + if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "installers", name)); err != nil { return err } } @@ -130,11 +127,6 @@ func (dd *DownloadDependencies) DownloadInstallers(installers distribution.Manif } func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) error { - basePath, err := os.Getwd() - if err != nil { - return err - } - tls := reflect.ValueOf(tools) unsupportedTools := []string{} @@ -142,22 +134,20 @@ func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) name := strings.ToLower(tls.Type().Field(i).Name) version := tls.Field(i).Interface().(string) - src, dstName := dd.fmtPaths(name, version, runtime.GOOS, "amd64") - if src == "" { + tool := dd.toolFactory.Create(name, version) + if tool == nil { unsupportedTools = append(unsupportedTools, name) continue } - dst := filepath.Join(basePath, "vendor", "bin", name) + dst := filepath.Join(dd.basePath, "vendor", "bin", name) - if err := dd.client.Download(src, dst); err != nil { + if err := dd.client.Download(tool.SrcPath(), dst); err != nil { return err } - if dstName != name { - if err := os.Rename(filepath.Join(dst, dstName), filepath.Join(dst, name)); err != nil { - return err - } + if err := tool.Rename(dst); err != nil { + return err } } @@ -167,46 +157,3 @@ func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) return nil } - -// fmtPaths returns the final location of the tool to download and the name of the downloaded binary -func (dd *DownloadDependencies) fmtPaths(toolName, toolVersion, osName, archName string) (string, string) { - if toolName == "furyagent" { - return fmt.Sprintf( - "https://github.com/sighupio/furyagent/releases/download/%s/furyagent-%s-%s", - semver.EnsurePrefix(toolVersion, "v"), - osName, - archName, - ), fmt.Sprintf("furyagent-%s-%s", osName, archName) - } - - if toolName == "kubectl" { - return fmt.Sprintf( - "https://dl.k8s.io/release/%s/bin/%s/%s/kubectl", - semver.EnsurePrefix(toolVersion, "v"), - osName, - archName, - ), "kubectl" - } - - if toolName == "kustomize" { - return fmt.Sprintf( - "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/%s/kustomize_%s_%s_%s.tar.gz", - semver.EnsurePrefix(toolVersion, "v"), - semver.EnsurePrefix(toolVersion, "v"), - osName, - archName, - ), "kustomize" - } - - if toolName == "terraform" { - return fmt.Sprintf( - "https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_%s.zip", - semver.EnsureNoPrefix(toolVersion, "v"), - semver.EnsureNoPrefix(toolVersion, "v"), - osName, - archName, - ), "terraform" - } - - return "", "" -} diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 473c11736..63be0162c 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -84,7 +84,6 @@ func Download( if !debug { defer osx.CleanupTempDir(filepath.Base(dst)) - } kfdPath := filepath.Join(dst, "kfd.yaml") diff --git a/internal/tools/furyagent.go b/internal/tools/furyagent.go new file mode 100644 index 000000000..521573b5d --- /dev/null +++ b/internal/tools/furyagent.go @@ -0,0 +1,40 @@ +package tools + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/sighupio/furyctl/internal/semver" +) + +func NewFuryAgent(version string) *FuryAgent { + return &FuryAgent{ + version: version, + os: runtime.GOOS, + arch: "amd64", + } +} + +type FuryAgent struct { + version string + os string + arch string +} + +func (f *FuryAgent) SrcPath() string { + return fmt.Sprintf( + "https://github.com/sighupio/furyagent/releases/download/%s/furyagent-%s-%s", + semver.EnsurePrefix(f.version, "v"), + f.os, + f.arch, + ) +} + +func (f *FuryAgent) Rename(basePath string) error { + oldName := fmt.Sprintf("furyagent-%s-%s", f.os, f.arch) + newName := "furyagent" + + return os.Rename(filepath.Join(basePath, oldName), filepath.Join(basePath, newName)) +} diff --git a/internal/tools/kubectl.go b/internal/tools/kubectl.go new file mode 100644 index 000000000..4516be6e5 --- /dev/null +++ b/internal/tools/kubectl.go @@ -0,0 +1,35 @@ +package tools + +import ( + "fmt" + "runtime" + + "github.com/sighupio/furyctl/internal/semver" +) + +func NewKubectl(version string) *Kubectl { + return &Kubectl{ + version: version, + os: runtime.GOOS, + arch: "amd64", + } +} + +type Kubectl struct { + version string + os string + arch string +} + +func (k *Kubectl) SrcPath() string { + return fmt.Sprintf( + "https://dl.k8s.io/release/%s/bin/%s/%s/kubectl", + semver.EnsurePrefix(k.version, "v"), + k.os, + k.arch, + ) +} + +func (f *Kubectl) Rename(basePath string) error { + return nil +} diff --git a/internal/tools/kustomize.go b/internal/tools/kustomize.go new file mode 100644 index 000000000..7fc99cd92 --- /dev/null +++ b/internal/tools/kustomize.go @@ -0,0 +1,36 @@ +package tools + +import ( + "fmt" + "runtime" + + "github.com/sighupio/furyctl/internal/semver" +) + +func NewKustomize(version string) *Kustomize { + return &Kustomize{ + version: version, + os: runtime.GOOS, + arch: "amd64", + } +} + +type Kustomize struct { + version string + os string + arch string +} + +func (k *Kustomize) SrcPath() string { + return fmt.Sprintf( + "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/%s/kustomize_%s_%s_%s.tar.gz", + semver.EnsurePrefix(k.version, "v"), + semver.EnsurePrefix(k.version, "v"), + k.os, + k.arch, + ) +} + +func (f *Kustomize) Rename(basePath string) error { + return nil +} diff --git a/internal/tools/terraform.go b/internal/tools/terraform.go new file mode 100644 index 000000000..b0e3ea05a --- /dev/null +++ b/internal/tools/terraform.go @@ -0,0 +1,36 @@ +package tools + +import ( + "fmt" + "runtime" + + "github.com/sighupio/furyctl/internal/semver" +) + +func NewTerraform(version string) *Terraform { + return &Terraform{ + version: version, + os: runtime.GOOS, + arch: "amd64", + } +} + +type Terraform struct { + version string + os string + arch string +} + +func (k *Terraform) SrcPath() string { + return fmt.Sprintf( + "https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_%s.zip", + semver.EnsureNoPrefix(k.version, "v"), + semver.EnsureNoPrefix(k.version, "v"), + k.os, + k.arch, + ) +} + +func (f *Terraform) Rename(basePath string) error { + return nil +} diff --git a/internal/tools/tool.go b/internal/tools/tool.go new file mode 100644 index 000000000..4b7396ee1 --- /dev/null +++ b/internal/tools/tool.go @@ -0,0 +1,28 @@ +package tools + +type Tool interface { + SrcPath() string + Rename(basePath string) error +} + +func NewFactory() *Factory { + return &Factory{} +} + +type Factory struct{} + +func (f *Factory) Create(name, version string) Tool { + if name == "furyagent" { + return NewFuryAgent(version) + } + if name == "kubectl" { + return NewKubectl(version) + } + if name == "kustomize" { + return NewKustomize(version) + } + if name == "terraform" { + return NewTerraform(version) + } + return nil +} From 9d7180a0375f299a1db928109206207835219c29 Mon Sep 17 00:00:00 2001 From: omissis Date: Fri, 23 Sep 2022 17:08:13 +0200 Subject: [PATCH 056/383] feat: put the final touches to download dependencies command, add tests --- Makefile | 3 +- cmd/download/dependencies.go | 20 +- cmd/validate.go | 3 +- cmd/validate/config.go | 7 +- cmd/validate/dependencies.go | 7 +- internal/app/common_test.go | 2 +- internal/app/download_dependencies.go | 56 +- internal/app/download_dependencies_test.go | 121 ++ internal/app/validate_config.go | 21 +- internal/app/validate_config_test.go | 3 +- internal/app/validate_dependencies.go | 13 +- internal/app/validate_dependencies_test.go | 9 +- internal/cobrax/flags_test.go | 222 +++ internal/distribution/download.go | 17 +- internal/distribution/download_test.go | 72 + internal/execx/exec.go | 5 +- internal/execx/exec_test.go | 29 + internal/osx/path.go | 4 + internal/tools/furyagent.go | 4 + internal/tools/furyagent_test.go | 70 + internal/tools/kubectl.go | 4 + internal/tools/kubectl_test.go | 67 + internal/tools/kustomize.go | 4 + internal/tools/kustomize_test.go | 70 + internal/tools/terraform.go | 4 + internal/tools/terraform_test.go | 70 + internal/tools/tool.go | 4 + internal/tools/tool_test.go | 51 + test/data/distro/v1.23.3/kfd.yaml | 26 + .../schemas/ekscluster-kfd-v1alpha2.json | 1310 +++++++++++++++++ test/data/furyctl.yaml | 246 ++++ test/integration/validation-cmd/tests.sh | 2 +- 32 files changed, 2478 insertions(+), 68 deletions(-) create mode 100644 internal/app/download_dependencies_test.go create mode 100644 internal/cobrax/flags_test.go create mode 100644 internal/execx/exec_test.go create mode 100644 internal/tools/furyagent_test.go create mode 100644 internal/tools/kubectl_test.go create mode 100644 internal/tools/kustomize_test.go create mode 100644 internal/tools/terraform_test.go create mode 100644 internal/tools/tool_test.go create mode 100644 test/data/distro/v1.23.3/kfd.yaml create mode 100644 test/data/distro/v1.23.3/schemas/ekscluster-kfd-v1alpha2.json create mode 100644 test/data/furyctl.yaml diff --git a/Makefile b/Makefile index d25ee4372..1300de775 100644 --- a/Makefile +++ b/Makefile @@ -109,9 +109,10 @@ lint-go: .PHONY: test-unit test-integration test-unit: - @go test -v -covermode=atomic -coverprofile=coverage.out ./... + @go test -v -covermode=atomic -coverprofile=coverage.out -tags=unit ./... test-integration: + @go test -v -covermode=atomic -coverprofile=coverage.out -tags=integration -timeout 120s ./... @bats -t ./test/integration/template-engine/tests.sh @bats -t ./test/integration/validation-cmd/tests.sh diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 3386b8633..352041f2a 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -5,8 +5,8 @@ package download import ( - "errors" "fmt" + "os" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -27,11 +27,13 @@ func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - dd, err := app.NewDownloadDependencies(netx.NewGoGetterClient()) + basePath, err := os.Getwd() if err != nil { return err } + dd := app.NewDownloadDependencies(netx.NewGoGetterClient(), basePath) + res, err := dd.Execute(app.DownloadDependenciesRequest{ FuryctlBinVersion: furyctlBinVersion, DistroLocation: distroLocation, @@ -39,22 +41,24 @@ func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { Debug: debug, }) if err != nil { - if !errors.Is(err, app.ErrUnsupportedTools) { - return err - } + return err + } - logrus.Warnf("unsupported tools: %v", err) + for _, ut := range res.UnsupTools { + logrus.Warn(fmt.Sprintf("'%s' download is not supported", ut)) } if res.HasErrors() { logrus.Debugf("Repository path: %s", res.RepoPath) - fmt.Println(res.Error) + for _, err := range res.DepsErrors { + logrus.Error(err) + } return ErrDownloadFailed } - fmt.Println("dependencies download succeeded") + logrus.Info("Dependencies download succeeded") return nil }, diff --git a/cmd/validate.go b/cmd/validate.go index accc8a7cf..e7c34881e 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" "github.com/sighupio/furyctl/cmd/validate" - "github.com/sighupio/furyctl/internal/execx" ) func NewValidateCommand(furyctlBinVersion string) *cobra.Command { @@ -18,7 +17,7 @@ func NewValidateCommand(furyctlBinVersion string) *cobra.Command { } validateCmd.AddCommand(validate.NewConfigCmd(furyctlBinVersion)) - validateCmd.AddCommand(validate.NewDependenciesCmd(furyctlBinVersion, execx.NewStdExecutor())) + validateCmd.AddCommand(validate.NewDependenciesCmd(furyctlBinVersion)) return validateCmd } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index bd18221b5..846f43563 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -12,6 +12,7 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" + "github.com/sighupio/furyctl/internal/netx" ) var ErrValidationFailed = fmt.Errorf("validation failed") @@ -25,7 +26,7 @@ func NewConfigCmd(furyctlBinVersion string) *cobra.Command { furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - vc := app.NewValidateConfig() + vc := app.NewValidateConfig(netx.NewGoGetterClient()) res, err := vc.Execute(app.ValidateConfigRequest{ FuryctlBinVersion: furyctlBinVersion, @@ -40,12 +41,12 @@ func NewConfigCmd(furyctlBinVersion string) *cobra.Command { if res.HasErrors() { logrus.Debugf("Repository path: %s", res.RepoPath) - fmt.Println(res.Error) + logrus.Error(res.Error) return ErrValidationFailed } - fmt.Println("Config validation succeeded") + logrus.Info("Config validation succeeded") return nil }, diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 15a213b9d..8bbc2d6cf 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -13,11 +13,12 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/execx" + "github.com/sighupio/furyctl/internal/netx" ) var ErrDependencies = fmt.Errorf("dependencies are not satisfied") -func NewDependenciesCmd(furyctlBinVersion string, executor execx.Executor) *cobra.Command { +func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", Short: "Validate furyctl.yaml file", @@ -27,7 +28,7 @@ func NewDependenciesCmd(furyctlBinVersion string, executor execx.Executor) *cobr furyctlPath := cobrax.Flag[string](cmd, "config").(string) distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - vd := app.NewValidateDependencies(executor) + vd := app.NewValidateDependencies(netx.NewGoGetterClient(), execx.NewStdExecutor()) res, err := vd.Execute(app.ValidateDependenciesRequest{ BinPath: binPath, @@ -50,7 +51,7 @@ func NewDependenciesCmd(furyctlBinVersion string, executor execx.Executor) *cobr return ErrDependencies } - fmt.Println("dependencies validation succeeded") + logrus.Info("Dependencies validation succeeded") return nil }, diff --git a/internal/app/common_test.go b/internal/app/common_test.go index e9065f3f4..2ae015a29 100644 --- a/internal/app/common_test.go +++ b/internal/app/common_test.go @@ -52,7 +52,7 @@ var ( FuryctlSchemas: distribution.ManifestSchemas{}, Tools: distribution.ManifestTools{ Ansible: "2.11.2", - Furyagent: "0.0.1", + Furyagent: "0.3.0", Kubectl: "1.21.1", Kustomize: "3.9.4", Terraform: "0.15.4", diff --git a/internal/app/download_dependencies.go b/internal/app/download_dependencies.go index a603343c4..eda8d1ab3 100644 --- a/internal/app/download_dependencies.go +++ b/internal/app/download_dependencies.go @@ -7,7 +7,6 @@ package app import ( "errors" "fmt" - "os" "path/filepath" "reflect" "strings" @@ -30,24 +29,20 @@ type DownloadDependenciesRequest struct { } type DownloadDependenciesResponse struct { - Error error - RepoPath string + DepsErrors []error + UnsupTools []string + RepoPath string } func (v DownloadDependenciesResponse) HasErrors() bool { - return v.Error != nil + return len(v.DepsErrors) > 0 } -func NewDownloadDependencies(client netx.Client) (*DownloadDependencies, error) { - basePath, err := os.Getwd() - if err != nil { - return nil, err - } - +func NewDownloadDependencies(client netx.Client, basePath string) *DownloadDependencies { return &DownloadDependencies{ client: client, basePath: basePath, - }, nil + } } type DownloadDependencies struct { @@ -57,30 +52,35 @@ type DownloadDependencies struct { } func (dd *DownloadDependencies) Execute(req DownloadDependenciesRequest) (DownloadDependenciesResponse, error) { - dres, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + dloader := distribution.NewDownloader(dd.client, req.Debug) + + dres, err := dloader.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath) if err != nil { return DownloadDependenciesResponse{}, err } + errs := []error{} if err := dd.DownloadModules(dres.DistroManifest.Modules); err != nil { - return DownloadDependenciesResponse{}, err + errs = append(errs, err) } if err := dd.DownloadInstallers(dres.DistroManifest.Kubernetes); err != nil { - return DownloadDependenciesResponse{}, err + errs = append(errs, err) } - if err := dd.DownloadTools(dres.DistroManifest.Tools); err != nil { - return DownloadDependenciesResponse{}, err + ut, err := dd.DownloadTools(dres.DistroManifest.Tools) + if err != nil { + errs = append(errs, err) } return DownloadDependenciesResponse{ - Error: nil, - RepoPath: dres.RepoPath, + DepsErrors: errs, + UnsupTools: ut, + RepoPath: dres.RepoPath, }, nil } func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestModules) error { - defaultPrefix := "https://github.com/sighupio/kubernetes-fury" - fallbackPrefix := "https://github.com/sighupio/fury-kubernetes" + newPrefix := "https://github.com/sighupio/kubernetes-fury" + oldPrefix := "https://github.com/sighupio/fury-kubernetes" mods := reflect.ValueOf(modules) @@ -89,7 +89,7 @@ func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestMod version := mods.Field(i).Interface().(string) errors := []error{} - for _, prefix := range []string{defaultPrefix, fallbackPrefix} { + for _, prefix := range []string{oldPrefix, newPrefix} { src := fmt.Sprintf("git::%s-%s.git?ref=%s", prefix, name, version) if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "modules", name)); err != nil { @@ -126,7 +126,7 @@ func (dd *DownloadDependencies) DownloadInstallers(installers distribution.Manif return nil } -func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) error { +func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) ([]string, error) { tls := reflect.ValueOf(tools) unsupportedTools := []string{} @@ -140,20 +140,16 @@ func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) continue } - dst := filepath.Join(dd.basePath, "vendor", "bin", name) + dst := filepath.Join(dd.basePath, "vendor", "bin") if err := dd.client.Download(tool.SrcPath(), dst); err != nil { - return err + return unsupportedTools, err } if err := tool.Rename(dst); err != nil { - return err + return unsupportedTools, err } } - if unsupportedTools != nil { - return fmt.Errorf("%w: %v", ErrUnsupportedTools, unsupportedTools) - } - - return nil + return unsupportedTools, nil } diff --git a/internal/app/download_dependencies_test.go b/internal/app/download_dependencies_test.go new file mode 100644 index 000000000..265915cc9 --- /dev/null +++ b/internal/app/download_dependencies_test.go @@ -0,0 +1,121 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build integration + +package app_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/sighupio/furyctl/internal/app" + "github.com/sighupio/furyctl/internal/netx" +) + +func TestDownloadDependencies(t *testing.T) { + testCases := []struct { + desc string + setup func(t *testing.T) (string, string) + teardown func(t *testing.T, tmpDir string) + wantErr bool + wantDepsErr bool + wantFiles []string + }{ + { + desc: "success", + setup: func(t *testing.T) (string, string) { + t.Helper() + + return setupDistroFolder(t, correctFuryctlDefaults, correctKFDConf) + }, + teardown: func(t *testing.T, tmpDir string) { + t.Helper() + + rmDirTemp(t, tmpDir) + }, + wantErr: false, + wantDepsErr: false, + wantFiles: []string{ + "vendor/bin/furyagent", + "vendor/bin/kubectl", + "vendor/bin/kustomize", + "vendor/bin/terraform", + "vendor/installers/eks/README.md", + "vendor/installers/eks/modules/eks/main.tf", + "vendor/installers/eks/modules/vpc-and-vpn/main.tf", + "vendor/modules/auth/README.md", + "vendor/modules/auth/katalog/gangway/kustomization.yaml", + "vendor/modules/dr/README.md", + "vendor/modules/dr/katalog/velero/velero-aws/kustomization.yaml", + "vendor/modules/ingress/README.md", + "vendor/modules/ingress/katalog/nginx/kustomization.yaml", + "vendor/modules/logging/README.md", + "vendor/modules/logging/katalog/configs/kustomization.yaml", + "vendor/modules/monitoring/README.md", + "vendor/modules/monitoring/katalog/configs/kustomization.yaml", + "vendor/modules/opa/README.md", + "vendor/modules/opa/katalog/gatekeeper/kustomization.yaml", + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + tmpDir, configFilePath := tC.setup(t) + + defer tC.teardown(t, tmpDir) + + basePath, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating tmp dir for test: %v", err) + } + defer os.RemoveAll(basePath) + + t.Logf("basePath: %s", basePath) + + vc := app.NewDownloadDependencies(netx.NewGoGetterClient(), basePath) + + res, err := vc.Execute(app.DownloadDependenciesRequest{ + FuryctlBinVersion: "unknown", + DistroLocation: tmpDir, + FuryctlConfPath: configFilePath, + Debug: true, + }) + + if tC.wantErr && err == nil { + t.Error("expected error, got nil") + } + if !tC.wantErr && err != nil { + t.Errorf("unexpected error, got = %v", err) + } + + if tC.wantDepsErr && len(res.DepsErrors) == 0 { + t.Fatal("expected deps download error, got none") + } + if !tC.wantDepsErr && len(res.DepsErrors) != 0 { + t.Fatalf("unexpected deps download error, got = %v", res.DepsErrors) + } + + for _, f := range tC.wantFiles { + info, err := os.Stat(filepath.Join(basePath, f)) + if err != nil { + if os.IsNotExist(err) { + t.Errorf("expected file %s to exist, but it doesn't", f) + } else { + t.Fatalf("unexpected error with file '%s': %v", f, err) + } + + continue + } + + if info.IsDir() { + t.Errorf("expected '%s' to be a file, it's a directory instead", f) + } + } + }) + } +} diff --git a/internal/app/validate_config.go b/internal/app/validate_config.go index 6e2bd4a9a..e6a03f371 100644 --- a/internal/app/validate_config.go +++ b/internal/app/validate_config.go @@ -11,6 +11,7 @@ import ( "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/netx" "github.com/sighupio/furyctl/internal/osx" "github.com/sighupio/furyctl/internal/schema/santhosh" "github.com/sighupio/furyctl/internal/yaml" @@ -32,14 +33,20 @@ func (v ValidateConfigResponse) HasErrors() bool { return v.Error != nil } -func NewValidateConfig() *ValidateConfig { - return &ValidateConfig{} +func NewValidateConfig(client netx.Client) *ValidateConfig { + return &ValidateConfig{ + client: client, + } +} + +type ValidateConfig struct { + client netx.Client } -type ValidateConfig struct{} +func (vc *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigResponse, error) { + dloader := distribution.NewDownloader(vc.client, req.Debug) -func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigResponse, error) { - res, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + res, err := dloader.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath) if err != nil { return ValidateConfigResponse{}, err } @@ -51,7 +58,7 @@ func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigRespo defaultPath := distribution.GetDefaultPath(res.RepoPath) - defaultedFuryctlConfPath, err := h.mergeConfigAndDefaults(req.FuryctlConfPath, defaultPath) + defaultedFuryctlConfPath, err := vc.mergeConfigAndDefaults(req.FuryctlConfPath, defaultPath) if err != nil { return ValidateConfigResponse{}, err } @@ -79,7 +86,7 @@ func (h *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigRespo return ValidateConfigResponse{}, nil } -func (h *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { +func (vc *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) if err != nil { return "", fmt.Errorf("%w: %v", distribution.ErrYamlUnmarshalFile, err) diff --git a/internal/app/validate_config_test.go b/internal/app/validate_config_test.go index da28e853d..f0bc1ec2f 100644 --- a/internal/app/validate_config_test.go +++ b/internal/app/validate_config_test.go @@ -15,6 +15,7 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/netx" "github.com/sighupio/furyctl/internal/yaml" ) @@ -158,7 +159,7 @@ func TestValidateConfig(t *testing.T) { defer tC.teardown(t, tmpDir) - vc := app.NewValidateConfig() + vc := app.NewValidateConfig(netx.NewGoGetterClient()) res, err := vc.Execute(app.ValidateConfigRequest{ FuryctlBinVersion: "unknown", DistroLocation: tmpDir, diff --git a/internal/app/validate_dependencies.go b/internal/app/validate_dependencies.go index d4ff0a424..97882e10b 100644 --- a/internal/app/validate_dependencies.go +++ b/internal/app/validate_dependencies.go @@ -14,6 +14,7 @@ import ( "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/execx" + "github.com/sighupio/furyctl/internal/netx" ) var ( @@ -43,18 +44,24 @@ func (vdr *ValidateDependenciesResponse) HasErrors() bool { return len(vdr.Errors) > 0 } -func NewValidateDependencies(executor execx.Executor) *ValidateDependencies { - return &ValidateDependencies{executor: executor} +func NewValidateDependencies(client netx.Client, executor execx.Executor) *ValidateDependencies { + return &ValidateDependencies{ + client: client, + executor: executor, + } } type ValidateDependencies struct { + client netx.Client executor execx.Executor } func (vd *ValidateDependencies) Execute(req ValidateDependenciesRequest) (ValidateDependenciesResponse, error) { + dloader := distribution.NewDownloader(vd.client, req.Debug) + res := ValidateDependenciesResponse{} - dres, err := distribution.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath, req.Debug) + dres, err := dloader.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath) if err != nil { return res, err } diff --git a/internal/app/validate_dependencies_test.go b/internal/app/validate_dependencies_test.go index af9916a84..d067baec7 100644 --- a/internal/app/validate_dependencies_test.go +++ b/internal/app/validate_dependencies_test.go @@ -15,11 +15,13 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/execx" + "github.com/sighupio/furyctl/internal/netx" ) func TestValidateDependencies(t *testing.T) { testCases := []struct { desc string + client netx.Client executor execx.Executor envs map[string]string kfdConf distribution.Manifest @@ -29,6 +31,7 @@ func TestValidateDependencies(t *testing.T) { }{ { desc: "missing tools and envs", + client: netx.NewGoGetterClient(), executor: execx.NewStdExecutor(), kfdConf: correctKFDConf, wantErrCount: 8, @@ -37,6 +40,7 @@ func TestValidateDependencies(t *testing.T) { }, { desc: "has all tools and envs", + client: netx.NewGoGetterClient(), executor: execx.NewFakeExecutor(), kfdConf: correctKFDConf, envs: map[string]string{ @@ -48,6 +52,7 @@ func TestValidateDependencies(t *testing.T) { }, { desc: "has wrong tools", + client: netx.NewGoGetterClient(), executor: execx.NewFakeExecutor(), kfdConf: wrongKFDConf, envs: map[string]string{ @@ -70,7 +75,7 @@ func TestValidateDependencies(t *testing.T) { t.Setenv(k, v) } - vd := app.NewValidateDependencies(tC.executor) + vd := app.NewValidateDependencies(tC.client, tC.executor) res, err := vd.Execute(app.ValidateDependenciesRequest{ BinPath: filepath.Join(tmpDir, "bin"), @@ -133,7 +138,7 @@ func TestHelperProcess(t *testing.T) { fmt.Fprintf(os.Stdout, "Version: {kustomize/v3.9.4 GitCommit:xxxxxxx"+ "BuildDate:2021-05-12T14:00:00Z GoOs:darwin GoArch:amd64}") case "furyagent": - fmt.Fprintf(os.Stdout, "furyagent version 0.0.1") + fmt.Fprintf(os.Stdout, "furyagent version 0.3.0") default: fmt.Fprintf(os.Stdout, "command not found") } diff --git a/internal/cobrax/flags_test.go b/internal/cobrax/flags_test.go new file mode 100644 index 000000000..c1f7d2fe3 --- /dev/null +++ b/internal/cobrax/flags_test.go @@ -0,0 +1,222 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cobrax_test + +import ( + "testing" + + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/cobrax" +) + +func Test_Flag_Bool(t *testing.T) { + testCases := []struct { + desc string + cmd *cobra.Command + args []string + name string + want bool + }{ + { + desc: "empty cobra command", + cmd: nil, + name: "", + want: false, + }, + { + desc: "non-existing flag", + cmd: &cobra.Command{}, + name: "wrong", + want: false, + }, + { + desc: "existing flag, value true", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().BoolP("debug", "D", false, "Enables furyctl debug output") + + return cmd + }(), + args: []string{"--debug=true"}, + name: "debug", + want: true, + }, + { + desc: "existing flag, value false", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().BoolP("debug", "D", false, "Enables furyctl debug output") + + return cmd + }(), + args: []string{"--debug=false"}, + name: "debug", + want: false, + }, + { + desc: "existing flag, wrong value", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().BoolP("debug", "D", false, "Enables furyctl debug output") + + return cmd + }(), + args: []string{"--debug=wrong"}, + name: "debug", + want: false, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + if tC.cmd != nil { + tC.cmd.SetArgs(tC.args) + tC.cmd.Execute() + } + + if got := cobrax.Flag[bool](tC.cmd, tC.name); got != tC.want { + t.Errorf("expected %t, got %t", tC.want, got) + } + }) + } +} + +func Test_Flag_Int(t *testing.T) { + testCases := []struct { + desc string + cmd *cobra.Command + args []string + name string + want int + }{ + { + desc: "empty cobra command", + cmd: nil, + name: "", + want: 0, + }, + { + desc: "non-existing flag", + cmd: &cobra.Command{}, + name: "wrong", + want: 0, + }, + { + desc: "existing flag, value 123", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().IntP("count", "C", 123, "Number of times to run") + + return cmd + }(), + args: []string{"--count=123"}, + name: "count", + want: 123, + }, + { + desc: "existing flag, value 0", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().IntP("count", "C", 0, "Number of times to run") + + return cmd + }(), + args: []string{"--count=0"}, + name: "count", + want: 0, + }, + { + desc: "existing flag, wrong value", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().IntP("count", "C", 0, "Number of times to run") + + return cmd + }(), + args: []string{"--count=wrong"}, + name: "count", + want: 0, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + if tC.cmd != nil { + tC.cmd.SetArgs(tC.args) + tC.cmd.Execute() + } + + if got := cobrax.Flag[int](tC.cmd, tC.name); got != tC.want { + t.Errorf("expected %d, got %d", tC.want, got) + } + }) + } +} + +func Test_Flag_String(t *testing.T) { + testCases := []struct { + desc string + cmd *cobra.Command + args []string + name string + want string + }{ + { + desc: "empty cobra command", + cmd: nil, + name: "", + want: "", + }, + { + desc: "non-existing flag", + cmd: &cobra.Command{}, + name: "wrong", + want: "", + }, + { + desc: "existing flag, value bar", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().StringP("foo", "F", "", "Example") + + return cmd + }(), + args: []string{"--foo=bar"}, + name: "foo", + want: "bar", + }, + { + desc: "existing flag, empty value", + cmd: func() *cobra.Command { + cmd := &cobra.Command{} + + cmd.Flags().StringP("foo", "F", "", "Example") + + return cmd + }(), + args: []string{"--foo="}, + name: "foo", + want: "", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + if tC.cmd != nil { + tC.cmd.SetArgs(tC.args) + tC.cmd.Execute() + } + + if got := cobrax.Flag[string](tC.cmd, tC.name); got != tC.want { + t.Errorf("expected %s, got %s", tC.want, got) + } + }) + } +} diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 63be0162c..10b02f48e 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -37,11 +37,22 @@ type downloadResult struct { DistroManifest Manifest } -func Download( +func NewDownloader(client netx.Client, debug bool) *Downloader { + return &Downloader{ + client: client, + debug: debug, + } +} + +type Downloader struct { + client netx.Client + debug bool +} + +func (d *Downloader) Download( furyctlBinVersion string, distroLocation string, furyctlConfPath string, - debug bool, ) (downloadResult, error) { minimalConf, err := yaml.FromFileV3[FuryctlConfig](furyctlConfPath) if err != nil { @@ -82,7 +93,7 @@ func Download( return downloadResult{}, fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } - if !debug { + if !d.debug { defer osx.CleanupTempDir(filepath.Base(dst)) } diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index 16afb06ff..03cc57d13 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -10,9 +10,81 @@ import ( "testing" "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/netx" "github.com/sighupio/furyctl/internal/semver" ) +func Test_Downloader_Download(t *testing.T) { + testCases := []struct { + desc string + furyctlBinVer string + wantApiVer string + wantKind string + wantDistroVer string + }{ + { + desc: "unknown furyctl version", + furyctlBinVer: "unknown", + wantApiVer: "kfd.sighup.io/v1alpha2", + wantKind: "EKSCluster", + wantDistroVer: "v1.23.3", + }, + { + desc: "compatible furyctl version", + furyctlBinVer: "1.23.0", + wantApiVer: "kfd.sighup.io/v1alpha2", + wantKind: "EKSCluster", + wantDistroVer: "v1.23.3", + }, + { + desc: "older furyctl version", + furyctlBinVer: "1.20.0", + wantApiVer: "kfd.sighup.io/v1alpha2", + wantKind: "EKSCluster", + wantDistroVer: "v1.23.3", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + distroLocation, err := filepath.Abs("../../test/data/distro/" + tC.wantDistroVer) + if err != nil { + t.Fatal(err) + } + + d := distribution.NewDownloader(netx.NewGoGetterClient(), true) + + res, err := d.Download(tC.furyctlBinVer, distroLocation, "../../test/data/furyctl.yaml") + if err != nil { + t.Fatal(err) + } + + if res.RepoPath == "" { + t.Errorf("expected RepoPath, got empty string") + } + + if res.MinimalConf.ApiVersion != tC.wantApiVer { + t.Errorf("ApiVersion: want = %s, got = %s", tC.wantApiVer, res.MinimalConf.ApiVersion) + } + if res.MinimalConf.Kind.String() != tC.wantKind { + t.Errorf("Kind: want = %s, got = %s", tC.wantKind, res.MinimalConf.Kind) + } + if res.MinimalConf.Spec.DistributionVersion != semver.Version(tC.wantDistroVer) { + t.Errorf( + "DistributionVersion: want = %s, got = %s", + tC.wantDistroVer, + res.MinimalConf.Spec.DistributionVersion, + ) + } + + if res.DistroManifest.Version != semver.Version(tC.wantDistroVer) { + t.Errorf("ApiVersion: want = %s, got = %s", tC.wantDistroVer, res.DistroManifest.Version) + } + }) + } +} + func TestGetSchemaPath(t *testing.T) { tests := []struct { name string diff --git a/internal/execx/exec.go b/internal/execx/exec.go index 049aef847..bb3414138 100644 --- a/internal/execx/exec.go +++ b/internal/execx/exec.go @@ -30,9 +30,8 @@ func NewFakeExecutor() *FakeExecutor { type FakeExecutor struct{} -func (e *FakeExecutor) Command(path string, arg ...string) *exec.Cmd { - name := filepath.Base(path) - cs := []string{"-test.run=TestHelperProcess", "--", name} +func (e *FakeExecutor) Command(name string, arg ...string) *exec.Cmd { + cs := []string{"-test.run=TestHelperProcess", "--", filepath.Base(name)} cs = append(cs, arg...) return exec.Command(os.Args[0], cs...) diff --git a/internal/execx/exec_test.go b/internal/execx/exec_test.go new file mode 100644 index 000000000..43401e2de --- /dev/null +++ b/internal/execx/exec_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package execx_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/execx" +) + +func Test_StdExecutor_Command(t *testing.T) { + e := execx.NewStdExecutor() + + cmd := e.Command("echo", "hello go world") + if cmd == nil { + t.Fatalf("expected command to be not nil") + } + + out, err := cmd.Output() + if err != nil { + t.Fatalf("expected command to be executed without errors: %v", err) + } + + if string(out) != "hello go world\n" { + t.Errorf("want = 'hello go world', got = '%s'", string(out)) + } +} diff --git a/internal/osx/path.go b/internal/osx/path.go index 6d5b3a272..984df6ef1 100644 --- a/internal/osx/path.go +++ b/internal/osx/path.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package osx import ( diff --git a/internal/tools/furyagent.go b/internal/tools/furyagent.go index 521573b5d..afde946fb 100644 --- a/internal/tools/furyagent.go +++ b/internal/tools/furyagent.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tools import ( diff --git a/internal/tools/furyagent_test.go b/internal/tools/furyagent_test.go new file mode 100644 index 000000000..f401f8fa0 --- /dev/null +++ b/internal/tools/furyagent_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/sighupio/furyctl/internal/tools" +) + +func Test_FuryAgent_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf( + "https://github.com/sighupio/furyagent/releases/download/v0.3.0/furyagent-%s-amd64", + runtime.GOOS, + ) + + testCases := []struct { + desc string + version string + }{ + { + desc: "0.3.0", + version: "0.3.0", + }, + { + desc: "v0.3.0", + version: "v0.3.0", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewFuryAgent(tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong furyagent src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_FuryAgent_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf("furyagent-%s-amd64", runtime.GOOS))); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewFuryAgent("0.3.0") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming furyagent binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "furyagent")) + if err != nil { + t.Fatalf("Error stating furyagent binary: %v", err) + } + + if info.IsDir() { + t.Errorf("furyagent binary is a directory") + } +} diff --git a/internal/tools/kubectl.go b/internal/tools/kubectl.go index 4516be6e5..537792673 100644 --- a/internal/tools/kubectl.go +++ b/internal/tools/kubectl.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tools import ( diff --git a/internal/tools/kubectl_test.go b/internal/tools/kubectl_test.go new file mode 100644 index 000000000..fc504847b --- /dev/null +++ b/internal/tools/kubectl_test.go @@ -0,0 +1,67 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/sighupio/furyctl/internal/tools" +) + +func Test_Kubectl_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf("https://dl.k8s.io/release/v1.23.10/bin/%s/amd64/kubectl", runtime.GOOS) + + testCases := []struct { + desc string + version string + }{ + { + desc: "1.23.10", + version: "1.23.10", + }, + { + desc: "v1.23.10", + version: "v1.23.10", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewKubectl(tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong kubectl src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_Kubectl_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, "kubectl")); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewKubectl("1.23.10") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming kubectl binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "kubectl")) + if err != nil { + t.Fatalf("Error stating kubectl binary: %v", err) + } + + if info.IsDir() { + t.Errorf("kubectl binary is a directory") + } +} diff --git a/internal/tools/kustomize.go b/internal/tools/kustomize.go index 7fc99cd92..5654ffcd3 100644 --- a/internal/tools/kustomize.go +++ b/internal/tools/kustomize.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tools import ( diff --git a/internal/tools/kustomize_test.go b/internal/tools/kustomize_test.go new file mode 100644 index 000000000..316ff84f5 --- /dev/null +++ b/internal/tools/kustomize_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/sighupio/furyctl/internal/tools" +) + +func Test_Kustomize_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf( + "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.10.0/kustomize_v3.10.0_%s_amd64.tar.gz", + runtime.GOOS, + ) + + testCases := []struct { + desc string + version string + }{ + { + desc: "3.10.0", + version: "3.10.0", + }, + { + desc: "v3.10.0", + version: "v3.10.0", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewKustomize(tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong kustomize src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_Kustomize_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, "kustomize")); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewKustomize("3.10.0") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming kustomize binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "kustomize")) + if err != nil { + t.Fatalf("Error stating kustomize binary: %v", err) + } + + if info.IsDir() { + t.Errorf("kustomize binary is a directory") + } +} diff --git a/internal/tools/terraform.go b/internal/tools/terraform.go index b0e3ea05a..fbd9ea383 100644 --- a/internal/tools/terraform.go +++ b/internal/tools/terraform.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tools import ( diff --git a/internal/tools/terraform_test.go b/internal/tools/terraform_test.go new file mode 100644 index 000000000..071054325 --- /dev/null +++ b/internal/tools/terraform_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/sighupio/furyctl/internal/tools" +) + +func Test_Terraform_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf( + "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_%s_amd64.zip", + runtime.GOOS, + ) + + testCases := []struct { + desc string + version string + }{ + { + desc: "1.2.9", + version: "1.2.9", + }, + { + desc: "v1.2.9", + version: "v1.2.9", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewTerraform(tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong terraform src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_Terraform_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, "terraform")); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewTerraform("1.2.9") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming terraform binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "terraform")) + if err != nil { + t.Fatalf("Error stating terraform binary: %v", err) + } + + if info.IsDir() { + t.Errorf("terraform binary is a directory") + } +} diff --git a/internal/tools/tool.go b/internal/tools/tool.go index 4b7396ee1..236f9de7c 100644 --- a/internal/tools/tool.go +++ b/internal/tools/tool.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tools type Tool interface { diff --git a/internal/tools/tool_test.go b/internal/tools/tool_test.go new file mode 100644 index 000000000..9596eb982 --- /dev/null +++ b/internal/tools/tool_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/tools" +) + +func Test_Factory_Create(t *testing.T) { + testCases := []struct { + desc string + wantTool bool + }{ + { + desc: "furyagent", + wantTool: true, + }, + { + desc: "kubectl", + wantTool: true, + }, + { + desc: "kustomize", + wantTool: true, + }, + { + desc: "terraform", + wantTool: true, + }, + { + desc: "unsupported", + wantTool: false, + }, + } + for _, tC := range testCases { + f := tools.NewFactory() + t.Run(tC.desc, func(t *testing.T) { + tool := f.Create(tC.desc, "0.0.0") + if tool == nil && tC.wantTool { + t.Errorf("Expected tool %s, got nil", tC.desc) + } + if tool != nil && !tC.wantTool { + t.Errorf("Expected nil, got tool %s", tC.desc) + } + }) + } +} diff --git a/test/data/distro/v1.23.3/kfd.yaml b/test/data/distro/v1.23.3/kfd.yaml new file mode 100644 index 000000000..6983f5ab1 --- /dev/null +++ b/test/data/distro/v1.23.3/kfd.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.23.3 +modules: + auth: v0.0.1 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 +kubernetes: + eks: + version: 1.23 + installer: v1.9.1 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + ansible: 2.9.27 + furyagent: 0.3.0 + kubectl: 1.23.10 + kustomize: 3.10.0 + terraform: 0.15.4 diff --git a/test/data/distro/v1.23.3/schemas/ekscluster-kfd-v1alpha2.json b/test/data/distro/v1.23.3/schemas/ekscluster-kfd-v1alpha2.json new file mode 100644 index 000000000..f575359ad --- /dev/null +++ b/test/data/distro/v1.23.3/schemas/ekscluster-kfd-v1alpha2.json @@ -0,0 +1,1310 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["EKSCluster"] + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "$defs": { + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "distributionVersion": { + "$ref": "#/$defs/Types.SemVer" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "toolsConfiguration": { + "$ref": "#/$defs/Spec.ToolsConfiguration" + }, + "infrastructure": { + "$ref": "#/$defs/Spec.Infrastructure" + }, + "kubernetes": { + "type": "object", + "additionalProperties": true + }, + "distribution": { + "$ref": "#/$defs/Spec.Distribution" + } + }, + "required": [ + "distributionVersion", + "kubernetes", + "toolsConfiguration" + ] + }, + + "Spec.ToolsConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" + } + }, + "required": [ + "terraform" + ] + }, + "Spec.ToolsConfiguration.Terraform": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" + } + }, + "required": [ + "state" + ] + }, + "Spec.ToolsConfiguration.Terraform.State": { + "type": "object", + "additionalProperties": false, + "properties": { + "s3": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" + } + }, + "required": [ + "s3" + ] + }, + "Spec.ToolsConfiguration.Terraform.State.S3": { + "type": "object", + "additionalProperties": false, + "properties": { + "bucketName": { + "type": "string" + }, + "keyPrefix": { + "type": "string" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "bucketName", + "keyPrefix", + "region" + ] + }, + + "Spec.Infrastructure": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpc": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc" + } + } + }, + "Spec.Infrastructure.Vpc": { + "type": "object", + "additionalProperties": false, + "properties": { + "network": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" + } + }, + "required": [ + "network" + ] + }, + "Spec.Infrastructure.Vpc.Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "subnetsCidrs": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" + } + }, + "required": [ + "cidr", + "subnetsCidrs" + ] + }, + "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { + "type": "object", + "additionalProperties": false, + "properties": { + "private": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "public": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "private", + "public" + ] + }, + "Spec.Infrastructure.Vpc.Vpn": { + "type": "object", + "additionalProperties": false, + "properties": { + "instances": { + "type": "integer" + }, + "port": { + "$ref": "#/$defs/Types.TcpPort" + }, + "instanceType": { + "type": "string" + }, + "diskSize": { + "type": "integer" + }, + "operatorName": { + "type": "string" + }, + "dhParamsBits": { + "type": "integer" + }, + "vpnClientsSubnetCidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "ssh": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + } + }, + "required": [ + "dhParamsBits", + "diskSize", + "instances", + "instanceType", + "operatorName", + "port", + "ssh", + "vpnClientsSubnetCidr" + ] + }, + "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "type": "object", + "additionalProperties": false, + "properties": { + "publicKeys": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + } + }, + "githubUsersName": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedFromCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedFromCidrs", + "githubUsersName", + "publicKeys" + ] + }, + + "Spec.Kubernetes": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "apiServerEndpointAccess": { + "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + }, + "nodeAllowedSshPublicKey": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + }, + "nodePools": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool" + } + }, + "awsAuth": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" + } + }, + "required": [ + "apiServerEndpointAccess", + "awsAuth", + "nodeAllowedSshPublicKey", + "nodePools", + "subnetIds", + "vpcId" + ] + }, + "Spec.Kubernetes.APIServerEndpointAccess": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "public", + "private", + "public_and_private" + ] + }, + "allowedCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedCidrs", + "type" + ] + }, + "Spec.Kubernetes.NodePool": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "ami": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" + }, + "size": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" + }, + "instance": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" + }, + "attachedTargetGroups": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "taints": { + "$ref": "#/$defs/Types.KubeTaints" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "additionalFirewallRules": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" + } + } + }, + "required": [ + "additionalFirewallRules", + "ami", + "attachedTargetGroups", + "instance", + "labels", + "name", + "size", + "tags", + "taints" + ] + }, + "Spec.Kubernetes.NodePool.Ami": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "owner" + ] + }, + "Spec.Kubernetes.NodePool.Instance": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "spot": { + "type": "boolean" + }, + "volumeSize": { + "type": "integer" + } + }, + "required": [ + "spot", + "type", + "volumeSize" + ] + }, + "Spec.Kubernetes.NodePool.Size": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer", + "minimum": 1 + }, + "max": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "max", + "min" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/$defs/Types.TcpPort" + }, + "to": { + "$ref": "#/$defs/Types.TcpPort" + } + }, + "required": [ + "from", + "to" + ] + }, + "Spec.Kubernetes.AwsAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" + } + } + }, + "required": [ + "additionalAccounts", + "roles", + "users" + ] + }, + "Spec.Kubernetes.AwsAuth.Role": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "rolearn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "rolearn", + "username" + ] + }, + "Spec.Kubernetes.AwsAuth.User": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "userarn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "userarn", + "username" + ] + }, + + "Spec.Distribution": { + "type": "object", + "additionalProperties": false, + "properties": { + "common": { + "$ref": "#/$defs/Spec.Distribution.Common" + }, + "modules": { + "$ref": "#/$defs/Spec.Distribution.Modules" + } + }, + "required": [ + "common", + "modules" + ] + }, + "Spec.Distribution.Common": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Common.Provider" + }, + "relativeVendorPath": { + "type": "string" + } + }, + "required": [ + "nodeSelector", + "tolerations", + "provider", + "relativeVendorPath" + ] + }, + "Spec.Distribution.Common.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth" + }, + "dr": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr" + }, + "ingress": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" + }, + "logging": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging" + }, + "monitoring": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" + }, + "policy": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy" + }, + "aws": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws" + } + }, + "required": [ + "auth", + "dr", + "ingress", + "logging", + "monitoring", + "policy" + ] + }, + "Spec.Distribution.Modules.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "baseDomain": { + "type": "string" + }, + "nginx": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" + }, + "certManager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CERTManager" + }, + "dns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" + } + }, + "required": [ + "baseDomain", + "certManager", + "dns", + "nginx", + "overrides" + ] + }, + "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecastle": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" + } + } + }, + "Spec.Distribution.Modules.Ingress.Nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "dual"] + }, + "tls": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + } + }, + "required": [ + "tls", + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "enum": ["certManager", "secret", "none"] + }, + "secret": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" + } + }, + "required": [ + "provider", + "secret" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ca": { + "type": "string" + } + }, + "required": [ + "ca", + "cert", + "key" + ] + }, + "Spec.Distribution.Modules.Ingress.CERTManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "clusterIssuer": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer" + } + }, + "required": [ + "clusterIssuer" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["dns01", "http01"] + }, + "route53": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" + } + }, + "required": [ + "name", + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "hostedZoneId": { + "type": "string" + } + }, + "required": [ + "hostedZoneId", + "iamRoleArn", + "region" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "public": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" + }, + "private": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + } + }, + "required": [ + "public", + "private" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Public": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "name", + "create" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Private": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "vpcId": { + "type": "string" + } + }, + "required": [ + "enabled", + "name", + "vpcId" + ] + }, + "Spec.Distribution.Modules.Logging": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "opensearch": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + } + } + }, + "Spec.Distribution.Modules.Logging.Opensearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "triple"] + }, + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "storage_request": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Monitoring": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "prometheus": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" + } + } + }, + "Spec.Distribution.Modules.Monitoring.Prometheus": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "gatekeeper": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" + } + } + }, + "Spec.Distribution.Modules.Policy.Gatekeeper": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalExcludedNamespaces": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Spec.Distribution.Modules.Dr": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "velero": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" + } + } + }, + "Spec.Distribution.Modules.Dr.Velero": { + "type": "object", + "additionalProperties": false, + "properties": { + "eks": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + } + } + }, + "Spec.Distribution.Modules.Dr.Velero.Eks": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "bucket": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" + }, + "pomerium": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" + }, + "dex": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" + } + } + }, + + "Spec.Distribution.Modules.Auth.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingresses" + } + } + } + }, + "Spec.Distribution.Modules.Auth.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "host", + "ingressClass" + ] + }, + "Spec.Distribution.Modules.Auth.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["none", "basicAuth", "sso"] + }, + "basicAuth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" + } + }, + "required": [ + "secrets" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "COOKIE_SECRET": { + "type": "string" + }, + "IDP_CLIENT_SECRET": { + "type": "string" + }, + "SHARED_SECRET": { + "type": "string" + } + }, + "required": [ + "COOKIE_SECRET", + "IDP_CLIENT_SECRET", + "SHARED_SECRET" + ] + }, + "Spec.Distribution.Modules.Auth.Dex": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectors": { + "type": "array" + } + } + }, + "Spec.Distribution.Modules.Aws": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "clusterAutoscaler": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + } + } + }, + "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeGroupAutoDiscovery": { + "type": "string" + }, + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn", + "nodeGroupAutoDiscovery" + ] + }, + + "Types.SemVer": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "Types.IpAddress": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" + }, + "Types.Cidr": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" + }, + "Types.FileRef": { + "type": "string", + "pattern": "^\\{file\\:\\/\\/.+\\}$" + }, + "Types.EnvRef": { + "type": "string", + "pattern": "\\{^env\\:\\/\\/.*\\}$" + }, + "Types.TcpPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "Types.SshPubKey": { + "type": "string", + "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" + }, + "Types.Uri": { + "type": "string", + "pattern": "^(http|https)\\:\\/\\/.+$" + }, + "Types.AwsArn": { + "type": "string", + "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{16})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{16}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "enum": ["tcp", "udp", "icmp", "icmpv6", "-1"] + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" + } + } + } + }, + "Types.FuryModuleOverridesIngresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/test/data/furyctl.yaml b/test/data/furyctl.yaml new file mode 100644 index 000000000..8c9864001 --- /dev/null +++ b/test/data/furyctl.yaml @@ -0,0 +1,246 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: awesome-cluster-staging +spec: + distributionVersion: "v1.23.3" + toolsConfiguration: + terraform: + state: + s3: + bucketName: awesome-bucket-created-outside-furyctl + keyPrefix: furyctl/ + region: eu-west-1 + tags: + env: "staging" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.1.0.0/16 + subnetsCidrs: + private: + - 10.1.0.0/20 + - 10.1.16.0/20 + - 10.1.32.0/20 + public: + - 10.1.48.0/24 + - 10.1.49.0/24 + - 10.1.50.0/24 + vpn: + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 172.16.0.0/16 + ssh: + publicKeys: + - "ssh-ed25519 XYX" + - "{file://relative/path/to/ssh.pub}" + githubUsersName: + - lnovara + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + vpcId: vpc-0f92da9b4a2089963 + subnetIds: + - subnet-0ab84702287e38ccb + - subnet-0ae4e9199d9192226 + - subnet-01787e8da51e4f070 + apiServerEndpointAccess: + type: private + allowedCidrs: + - 10.1.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + ami: + id: null + owner: null + size: + min: 1 + max: 3 + instance: + type: t3.micro + spot: false + volumeSize: 50 + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: + - node.kubernetes.io/role=worker:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + awsAuth: + additionalAccounts: + - "777777777777" + - "88888888888" + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + distribution: + common: + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + modules: + ingress: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + forecastle: + disableAuth: false + host: "" + ingressClass: "" + baseDomain: internal.fury-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + secret: + cert: "{file://relative/path/to/ssl.crt}" + key: "{file://relative/path/to/ssl.key}" + ca: "{file://relative/path/to/ssl.ca}" + certManager: + clusterIssuer: + name: letsencrypt-fury + type: http01 + dns: + public: + enabled: true + name: "fury-demo.sighup.io" + create: false + private: + enabled: true + name: "internal.fury-demo.sighup.io" + vpcId: "vpc123123123123" + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + type: single + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + storage_request: "" + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + policy: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + gatekeeper: + additionalExcludedNamespaces: [] + dr: + overrides: + nodeSelector: {} + tolerations: [] + auth: + overrides: + nodeSelector: {} + ingresses: + pomerium: + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + type: none + basicAuth: + username: admin + password: "{env://KFD_BASIC_AUTH_PASSWORD}" + pomerium: + secrets: + COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" + IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" + SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" + dex: + connectors: + - type: github + id: github + name: GitHub + config: + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" + redirectURI: https://login.fury-demo.sighup.io/callback + loadAllGroups: false + teamNameField: slug + useLoginAsID: false diff --git a/test/integration/validation-cmd/tests.sh b/test/integration/validation-cmd/tests.sh index 824134238..4147279fa 100644 --- a/test/integration/validation-cmd/tests.sh +++ b/test/integration/validation-cmd/tests.sh @@ -147,5 +147,5 @@ FURYCTL="${PWD}/dist/furyctl" echo "${output}" >&3 fi - [[ "${output}" == *"dependencies validation succeeded"* ]] + [[ "${output}" == *"Dependencies validation succeeded"* ]] } From d006a3eff93a487523d55ea96f6113497faf4587 Mon Sep 17 00:00:00 2001 From: omissis Date: Fri, 23 Sep 2022 17:39:16 +0200 Subject: [PATCH 057/383] fix: integration tests --- internal/app/download_dependencies.go | 14 +- internal/app/download_dependencies_test.go | 25 +-- internal/distribution/download_test.go | 8 +- .../data/v1.23.3/distro/furyctl-defaults.yaml | 192 ++++++++++++++++++ .../v1.23.3 => v1.23.3/distro}/kfd.yaml | 0 .../schemas/ekscluster-kfd-v1alpha2.json | 0 test/data/{ => v1.23.3}/furyctl.yaml | 0 7 files changed, 218 insertions(+), 21 deletions(-) create mode 100644 test/data/v1.23.3/distro/furyctl-defaults.yaml rename test/data/{distro/v1.23.3 => v1.23.3/distro}/kfd.yaml (100%) rename test/data/{distro/v1.23.3 => v1.23.3/distro}/schemas/ekscluster-kfd-v1alpha2.json (100%) rename test/data/{ => v1.23.3}/furyctl.yaml (100%) diff --git a/internal/app/download_dependencies.go b/internal/app/download_dependencies.go index eda8d1ab3..7b372d3be 100644 --- a/internal/app/download_dependencies.go +++ b/internal/app/download_dependencies.go @@ -17,8 +17,10 @@ import ( ) var ( - ErrDownloadingModule = errors.New("error downloading module") - ErrUnsupportedTools = errors.New("unsupported tools") + ErrDownloadingModule = errors.New("error downloading module") + ErrUnsupportedTools = errors.New("unsupported tools") + ErrModuleHasNoVersion = errors.New("module has no version") + ErrModuleHasNoName = errors.New("module has no name") ) type DownloadDependenciesRequest struct { @@ -88,6 +90,14 @@ func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestMod name := strings.ToLower(mods.Type().Field(i).Name) version := mods.Field(i).Interface().(string) + if name == "" { + return ErrModuleHasNoName + } + + if version == "" { + return fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) + } + errors := []error{} for _, prefix := range []string{oldPrefix, newPrefix} { src := fmt.Sprintf("git::%s-%s.git?ref=%s", prefix, name, version) diff --git a/internal/app/download_dependencies_test.go b/internal/app/download_dependencies_test.go index 265915cc9..1187b3991 100644 --- a/internal/app/download_dependencies_test.go +++ b/internal/app/download_dependencies_test.go @@ -25,17 +25,7 @@ func TestDownloadDependencies(t *testing.T) { wantFiles []string }{ { - desc: "success", - setup: func(t *testing.T) (string, string) { - t.Helper() - - return setupDistroFolder(t, correctFuryctlDefaults, correctKFDConf) - }, - teardown: func(t *testing.T, tmpDir string) { - t.Helper() - - rmDirTemp(t, tmpDir) - }, + desc: "success", wantErr: false, wantDepsErr: false, wantFiles: []string{ @@ -65,10 +55,6 @@ func TestDownloadDependencies(t *testing.T) { tC := tC t.Run(tC.desc, func(t *testing.T) { - tmpDir, configFilePath := tC.setup(t) - - defer tC.teardown(t, tmpDir) - basePath, err := os.MkdirTemp("", "furyctl-test-") if err != nil { t.Fatalf("error creating tmp dir for test: %v", err) @@ -77,12 +63,17 @@ func TestDownloadDependencies(t *testing.T) { t.Logf("basePath: %s", basePath) + distroLocation, err := filepath.Abs("../../test/data/v1.23.3/distro") + if err != nil { + t.Fatal(err) + } + vc := app.NewDownloadDependencies(netx.NewGoGetterClient(), basePath) res, err := vc.Execute(app.DownloadDependenciesRequest{ FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, + DistroLocation: distroLocation, + FuryctlConfPath: "../../test/data/v1.23.3/furyctl.yaml", Debug: true, }) diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index 03cc57d13..02004c6e8 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -48,14 +48,18 @@ func Test_Downloader_Download(t *testing.T) { tC := tC t.Run(tC.desc, func(t *testing.T) { - distroLocation, err := filepath.Abs("../../test/data/distro/" + tC.wantDistroVer) + distroLocation, err := filepath.Abs(fmt.Sprintf("../../test/data/%s/distro", tC.wantDistroVer)) if err != nil { t.Fatal(err) } d := distribution.NewDownloader(netx.NewGoGetterClient(), true) - res, err := d.Download(tC.furyctlBinVer, distroLocation, "../../test/data/furyctl.yaml") + res, err := d.Download( + tC.furyctlBinVer, + distroLocation, + fmt.Sprintf("../../test/data/%s/furyctl.yaml", tC.wantDistroVer), + ) if err != nil { t.Fatal(err) } diff --git a/test/data/v1.23.3/distro/furyctl-defaults.yaml b/test/data/v1.23.3/distro/furyctl-defaults.yaml new file mode 100644 index 000000000..e1454aeaa --- /dev/null +++ b/test/data/v1.23.3/distro/furyctl-defaults.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: example.dev + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789012:role/example-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storage_request: "" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: [] + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: [] + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789012:role/example-velero + region: eu-west-1 + bucket: example-velero + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: "" + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: "" + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: "" + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/example + iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/test/data/distro/v1.23.3/kfd.yaml b/test/data/v1.23.3/distro/kfd.yaml similarity index 100% rename from test/data/distro/v1.23.3/kfd.yaml rename to test/data/v1.23.3/distro/kfd.yaml diff --git a/test/data/distro/v1.23.3/schemas/ekscluster-kfd-v1alpha2.json b/test/data/v1.23.3/distro/schemas/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/distro/v1.23.3/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/v1.23.3/distro/schemas/ekscluster-kfd-v1alpha2.json diff --git a/test/data/furyctl.yaml b/test/data/v1.23.3/furyctl.yaml similarity index 100% rename from test/data/furyctl.yaml rename to test/data/v1.23.3/furyctl.yaml From e121f1d6f5adddd6dc408e3204b08e58825e4714 Mon Sep 17 00:00:00 2001 From: omissis Date: Fri, 23 Sep 2022 17:46:30 +0200 Subject: [PATCH 058/383] chore: add NETRC secret to allow for private repos downloads --- .drone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.drone.yml b/.drone.yml index f3d9cb481..240a18436 100644 --- a/.drone.yml +++ b/.drone.yml @@ -61,6 +61,7 @@ steps: depends_on: - prepare commands: + - echo $${NETRC_FILE} > /root/.netrc - git clone --depth 1 --branch v1.8.0 https://github.com/bats-core/bats-core.git - ./bats-core/install.sh /usr/local - make test-integration @@ -69,6 +70,8 @@ steps: GOCACHE: /drone/src/.go/cache GOMODCACHE: /drone/src/.go/modcache GOTMPDIR: /drone/src/.go/tmp + NETRC_FILE: + from_secret: NETRC_FILE - name: build image: ghcr.io/goreleaser/goreleaser:v1.11.4 From 1a5de2f02fa96c0fcf5e90043e42e0a75cbb5027 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Fri, 30 Sep 2022 12:25:09 +0200 Subject: [PATCH 059/383] feat: add new release's update check --- internal/app/update.go | 38 +++++++++++++++++++++++++++++ internal/app/update_test.go | 48 +++++++++++++++++++++++++++++++++++++ main.go | 28 ++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 internal/app/update.go create mode 100644 internal/app/update_test.go diff --git a/internal/app/update.go b/internal/app/update.go new file mode 100644 index 000000000..5e2ec3f92 --- /dev/null +++ b/internal/app/update.go @@ -0,0 +1,38 @@ +package app + +import ( + "encoding/json" + "net/http" +) + +type Update struct { + FuryctlBinVersion string +} + +type Release struct { + URL string `json:"html_url"` + Version string `json:"name"` +} + +func NewUpdate(furyctlBinVersion string) *Update { + return &Update{ + FuryctlBinVersion: furyctlBinVersion, + } +} + +func (u *Update) FetchLastRelease() (Release, error) { + var release Release + + resp, err := http.Get("https://api.github.com/repos/sighupio/furyctl/releases/latest") + if err != nil { + return release, err + } + + defer resp.Body.Close() + + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return release, err + } + + return release, nil +} diff --git a/internal/app/update_test.go b/internal/app/update_test.go new file mode 100644 index 000000000..03cbcb97c --- /dev/null +++ b/internal/app/update_test.go @@ -0,0 +1,48 @@ +package app_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/app" +) + +func Test_Update_FetchLastRelease(t *testing.T) { + type fields struct { + FuryctlBinVersion string + } + tests := []struct { + name string + fields fields + want app.Release + }{ + { + name: "test", + fields: fields{ + FuryctlBinVersion: "unknown", + }, + want: app.Release{ + URL: "https://github.com/sighupio/furyctl/releases/tag/v0.8.0", + Version: "v0.8.0", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u := &app.Update{ + FuryctlBinVersion: tt.fields.FuryctlBinVersion, + } + got, err := u.FetchLastRelease() + if err != nil { + t.Fatal(err) + } + + t.Log(got) + + if got.Version != tt.want.Version { + t.Errorf("Update.FetchLastRelease() = %v, want %v", got, tt.want) + } + + }) + } +} diff --git a/main.go b/main.go index 9eb6241c1..0a0d30bf0 100644 --- a/main.go +++ b/main.go @@ -15,9 +15,12 @@ package main import ( + "sync" + "github.com/sirupsen/logrus" "github.com/sighupio/furyctl/cmd" + "github.com/sighupio/furyctl/internal/app" ) var ( @@ -28,6 +31,8 @@ var ( osArch = "unknown" ) +var wg sync.WaitGroup + func main() { versions := map[string]string{ "version": version, @@ -37,7 +42,30 @@ func main() { "osArch": osArch, } + wg.Add(1) + go checkUpdates(versions["version"]) + if err := cmd.NewRootCommand(versions).Execute(); err != nil { logrus.Fatal(err) } + + wg.Wait() +} + +func checkUpdates(version string) { + defer wg.Done() + + if version == "unknown" { + return + } + + u := app.NewUpdate(version) + r, err := u.FetchLastRelease() + if err != nil { + logrus.Debugf("Error fetching last release: %s", err) + } + + if r.Version != version { + logrus.Infof("New furyctl version available: %s --> %s", version, r.Version) + } } From 3aca44c013531313fb8d92cc98ecc242c15e403b Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Fri, 30 Sep 2022 12:36:00 +0200 Subject: [PATCH 060/383] chore: add missing license banner, fumpt and fmt format --- internal/app/update.go | 13 ++++++++++++- internal/app/update_test.go | 5 ++++- main.go | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/app/update.go b/internal/app/update.go index 5e2ec3f92..c6e78d944 100644 --- a/internal/app/update.go +++ b/internal/app/update.go @@ -1,6 +1,11 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app import ( + "context" "encoding/json" "net/http" ) @@ -23,7 +28,13 @@ func NewUpdate(furyctlBinVersion string) *Update { func (u *Update) FetchLastRelease() (Release, error) { var release Release - resp, err := http.Get("https://api.github.com/repos/sighupio/furyctl/releases/latest") + ctx := context.Background() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/sighupio/furyctl/releases/latest", nil) + if err != nil { + return release, err + } + + resp, err := http.DefaultClient.Do(req) if err != nil { return release, err } diff --git a/internal/app/update_test.go b/internal/app/update_test.go index 03cbcb97c..2a92680c1 100644 --- a/internal/app/update_test.go +++ b/internal/app/update_test.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package app_test import ( @@ -42,7 +46,6 @@ func Test_Update_FetchLastRelease(t *testing.T) { if got.Version != tt.want.Version { t.Errorf("Update.FetchLastRelease() = %v, want %v", got, tt.want) } - }) } } diff --git a/main.go b/main.go index 0a0d30bf0..316d76e27 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,8 @@ import ( ) var ( + wg sync.WaitGroup + version = "unknown" gitCommit = "unknown" buildTime = "unknown" @@ -31,8 +33,6 @@ var ( osArch = "unknown" ) -var wg sync.WaitGroup - func main() { versions := map[string]string{ "version": version, From a422b420658e29140c604456e3efb7771dd762c3 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Mon, 3 Oct 2022 10:59:31 +0200 Subject: [PATCH 061/383] chore: small refactor --- cmd/root.go | 34 ++++++++++++++++++++++++++++++++++ internal/app/update.go | 12 +----------- internal/app/update_test.go | 18 ++++-------------- main.go | 30 +----------------------------- 4 files changed, 40 insertions(+), 54 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 73a6ecf65..ccde08622 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,6 +13,7 @@ import ( "github.com/spf13/viper" "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/io" ) @@ -30,6 +31,10 @@ type RootCommand struct { } func NewRootCommand(versions map[string]string) *RootCommand { + // Update channels + r := make(chan app.Release, 1) + e := make(chan error, 1) + cfg := &rootConfig{} rootCmd := &RootCommand{ Command: &cobra.Command{ @@ -45,6 +50,8 @@ Furyctl is a simple CLI tool to: SilenceUsage: true, SilenceErrors: true, PersistentPreRun: func(cmd *cobra.Command, _ []string) { + // Async check for updates + go checkUpdates(versions["version"], r, e) // Configure the spinner w := logrus.StandardLogger().Out if cobrax.Flag[bool](cmd, "no-tty").(bool) { @@ -66,6 +73,17 @@ Furyctl is a simple CLI tool to: analytics.Version(versions["version"]) analytics.Disable(cobrax.Flag[bool](cmd, "disable-analytics").(bool)) }, + PersistentPostRun: func(cmd *cobra.Command, _ []string) { + // Show update message if available at the end of the command + select { + case release := <-r: + if release.Version != versions["version"] { + logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) + } + case err := <-e: + logrus.Debugf("Error checking for updates: %s", err) + } + }, }, config: cfg, } @@ -85,3 +103,19 @@ Furyctl is a simple CLI tool to: return rootCmd } + +func checkUpdates(version string, rc chan app.Release, e chan error) { + if version == "unknown" { + return + } + + r, err := app.GetLatestRelease() + if err != nil { + e <- err + return + } + + rc <- r + + close(rc) +} diff --git a/internal/app/update.go b/internal/app/update.go index c6e78d944..037f7553f 100644 --- a/internal/app/update.go +++ b/internal/app/update.go @@ -10,22 +10,12 @@ import ( "net/http" ) -type Update struct { - FuryctlBinVersion string -} - type Release struct { URL string `json:"html_url"` Version string `json:"name"` } -func NewUpdate(furyctlBinVersion string) *Update { - return &Update{ - FuryctlBinVersion: furyctlBinVersion, - } -} - -func (u *Update) FetchLastRelease() (Release, error) { +func GetLatestRelease() (Release, error) { var release Release ctx := context.Background() diff --git a/internal/app/update_test.go b/internal/app/update_test.go index 2a92680c1..5f2b57749 100644 --- a/internal/app/update_test.go +++ b/internal/app/update_test.go @@ -11,19 +11,12 @@ import ( ) func Test_Update_FetchLastRelease(t *testing.T) { - type fields struct { - FuryctlBinVersion string - } tests := []struct { - name string - fields fields - want app.Release + name string + want app.Release }{ { name: "test", - fields: fields{ - FuryctlBinVersion: "unknown", - }, want: app.Release{ URL: "https://github.com/sighupio/furyctl/releases/tag/v0.8.0", Version: "v0.8.0", @@ -33,10 +26,7 @@ func Test_Update_FetchLastRelease(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - u := &app.Update{ - FuryctlBinVersion: tt.fields.FuryctlBinVersion, - } - got, err := u.FetchLastRelease() + got, err := app.GetLatestRelease() if err != nil { t.Fatal(err) } @@ -44,7 +34,7 @@ func Test_Update_FetchLastRelease(t *testing.T) { t.Log(got) if got.Version != tt.want.Version { - t.Errorf("Update.FetchLastRelease() = %v, want %v", got, tt.want) + t.Errorf("FetchLastRelease() = %v, want %v", got, tt.want) } }) } diff --git a/main.go b/main.go index 316d76e27..784cb7f1e 100644 --- a/main.go +++ b/main.go @@ -15,18 +15,13 @@ package main import ( - "sync" - "github.com/sirupsen/logrus" "github.com/sighupio/furyctl/cmd" - "github.com/sighupio/furyctl/internal/app" ) var ( - wg sync.WaitGroup - - version = "unknown" + version = "0.2.0" gitCommit = "unknown" buildTime = "unknown" goVersion = "unknown" @@ -42,30 +37,7 @@ func main() { "osArch": osArch, } - wg.Add(1) - go checkUpdates(versions["version"]) - if err := cmd.NewRootCommand(versions).Execute(); err != nil { logrus.Fatal(err) } - - wg.Wait() -} - -func checkUpdates(version string) { - defer wg.Done() - - if version == "unknown" { - return - } - - u := app.NewUpdate(version) - r, err := u.FetchLastRelease() - if err != nil { - logrus.Debugf("Error fetching last release: %s", err) - } - - if r.Version != version { - logrus.Infof("New furyctl version available: %s --> %s", version, r.Version) - } } From 3bb58d201f84198fe66eccb98e2680849366dff6 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Wed, 5 Oct 2022 16:50:58 +0200 Subject: [PATCH 062/383] chore: add more semantic versions checks, fix stuck state with unknown version condition --- cmd/root.go | 4 ++- internal/app/update.go | 53 ++++++++++++++++++++++++++++++++++++- internal/app/update_test.go | 42 +++++++++++++++++++++++++++++ main.go | 2 +- 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ccde08622..80e4dd486 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -77,7 +77,7 @@ Furyctl is a simple CLI tool to: // Show update message if available at the end of the command select { case release := <-r: - if release.Version != versions["version"] { + if app.ShouldUpdate(versions["version"], release.Version) { logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) } case err := <-e: @@ -106,6 +106,8 @@ Furyctl is a simple CLI tool to: func checkUpdates(version string, rc chan app.Release, e chan error) { if version == "unknown" { + rc <- app.Release{Version: version} + close(rc) return } diff --git a/internal/app/update.go b/internal/app/update.go index 037f7553f..fb017d2b6 100644 --- a/internal/app/update.go +++ b/internal/app/update.go @@ -8,6 +8,9 @@ import ( "context" "encoding/json" "net/http" + "strconv" + "strings" + "time" ) type Release struct { @@ -15,10 +18,13 @@ type Release struct { Version string `json:"name"` } +// GetLatestRelease fetches the latest release from the GitHub API func GetLatestRelease() (Release, error) { var release Release - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/sighupio/furyctl/releases/latest", nil) if err != nil { return release, err @@ -37,3 +43,48 @@ func GetLatestRelease() (Release, error) { return release, nil } + +// ShouldUpdate checks if the current version is lower than the latest version +func ShouldUpdate(currentVersion, latestVersion string) bool { + // normalize the versions + nc := strings.TrimPrefix(currentVersion, "v") + nl := strings.TrimPrefix(latestVersion, "v") + + return compareVersions(nc, nl) +} + +// util func to compare two semantic versions, e.g: 0.8.0 vs 0.9.0 +func compareVersions(currentVersion, latestVersion string) bool { + if currentVersion == latestVersion { + return false + } + + // split the versions into slices of strings + currentVersionSlice := strings.Split(currentVersion, ".") + latestVersionSlice := strings.Split(latestVersion, ".") + + if len(currentVersionSlice) < len(latestVersionSlice) { + return true + } + + if len(currentVersionSlice) > len(latestVersionSlice) { + return false + } + + // compare the versions + for i := 0; i < len(currentVersionSlice); i++ { + c, _ := strconv.Atoi(currentVersionSlice[i]) + l, _ := strconv.Atoi(latestVersionSlice[i]) + + // if the current version is greater than the latest version + if c > l { + return false + } + // if the current version is less than the latest version + if c < l { + return true + } + } + + return false +} diff --git a/internal/app/update_test.go b/internal/app/update_test.go index 5f2b57749..6b863a87c 100644 --- a/internal/app/update_test.go +++ b/internal/app/update_test.go @@ -39,3 +39,45 @@ func Test_Update_FetchLastRelease(t *testing.T) { }) } } + +func Test_Update_MustUpdate(t *testing.T) { + tests := []struct { + name string + want bool + currentVersion string + latestVersion string + }{ + { + name: "Versions are equal, no update", + currentVersion: "v0.8.0", + latestVersion: "v0.8.0", + want: false, + }, + { + name: "Current version is higher than latest, no update", + currentVersion: "v0.10.0", + latestVersion: "v0.8.0", + want: false, + }, + { + name: "Current version's lenght is higher than latest, no update", + currentVersion: "v0.0.0.1", + latestVersion: "v0.0.0", + want: false, + }, + { + name: "Current version is lower than latest, update", + currentVersion: "v0.7.0", + latestVersion: "v0.8.0", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := app.ShouldUpdate(tt.currentVersion, tt.latestVersion); got != tt.want { + t.Errorf("%s = got %v, want %v", tt.name, got, tt.want) + } + }) + } +} diff --git a/main.go b/main.go index 784cb7f1e..9eb6241c1 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ import ( ) var ( - version = "0.2.0" + version = "unknown" gitCommit = "unknown" buildTime = "unknown" goVersion = "unknown" From 0aa8d66c384d2e3747632cde3e5f9538aaeb0ed2 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Wed, 5 Oct 2022 16:58:48 +0200 Subject: [PATCH 063/383] chore: small refactor --- cmd/root.go | 3 +++ internal/app/update.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 80e4dd486..07b73a479 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -77,6 +77,9 @@ Furyctl is a simple CLI tool to: // Show update message if available at the end of the command select { case release := <-r: + if release.Version == "unknown" { + return + } if app.ShouldUpdate(versions["version"], release.Version) { logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) } diff --git a/internal/app/update.go b/internal/app/update.go index fb017d2b6..b1900b8e5 100644 --- a/internal/app/update.go +++ b/internal/app/update.go @@ -44,7 +44,7 @@ func GetLatestRelease() (Release, error) { return release, nil } -// ShouldUpdate checks if the current version is lower than the latest version +// ShouldUpdate checks if the current version is outdated func ShouldUpdate(currentVersion, latestVersion string) bool { // normalize the versions nc := strings.TrimPrefix(currentVersion, "v") From f641563e935be13767c82cba9f88fcfcbaf3ceb7 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Wed, 5 Oct 2022 17:01:51 +0200 Subject: [PATCH 064/383] fix: rename update test --- internal/app/update_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/update_test.go b/internal/app/update_test.go index 6b863a87c..54110800d 100644 --- a/internal/app/update_test.go +++ b/internal/app/update_test.go @@ -40,7 +40,7 @@ func Test_Update_FetchLastRelease(t *testing.T) { } } -func Test_Update_MustUpdate(t *testing.T) { +func Test_Update_ShouldUpdate(t *testing.T) { tests := []struct { name string want bool From 5f581173dbb7afb20e14aecc09216c9513c70cc7 Mon Sep 17 00:00:00 2001 From: Luigi Barbato Date: Thu, 6 Oct 2022 14:47:33 +0200 Subject: [PATCH 065/383] chore: remove useless code --- cmd/root.go | 2 +- internal/app/update.go | 47 ------------------------------------- internal/app/update_test.go | 42 --------------------------------- 3 files changed, 1 insertion(+), 90 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 07b73a479..3ddca52c2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -80,7 +80,7 @@ Furyctl is a simple CLI tool to: if release.Version == "unknown" { return } - if app.ShouldUpdate(versions["version"], release.Version) { + if release.Version != versions["version"] { logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) } case err := <-e: diff --git a/internal/app/update.go b/internal/app/update.go index b1900b8e5..a672b11d0 100644 --- a/internal/app/update.go +++ b/internal/app/update.go @@ -8,8 +8,6 @@ import ( "context" "encoding/json" "net/http" - "strconv" - "strings" "time" ) @@ -43,48 +41,3 @@ func GetLatestRelease() (Release, error) { return release, nil } - -// ShouldUpdate checks if the current version is outdated -func ShouldUpdate(currentVersion, latestVersion string) bool { - // normalize the versions - nc := strings.TrimPrefix(currentVersion, "v") - nl := strings.TrimPrefix(latestVersion, "v") - - return compareVersions(nc, nl) -} - -// util func to compare two semantic versions, e.g: 0.8.0 vs 0.9.0 -func compareVersions(currentVersion, latestVersion string) bool { - if currentVersion == latestVersion { - return false - } - - // split the versions into slices of strings - currentVersionSlice := strings.Split(currentVersion, ".") - latestVersionSlice := strings.Split(latestVersion, ".") - - if len(currentVersionSlice) < len(latestVersionSlice) { - return true - } - - if len(currentVersionSlice) > len(latestVersionSlice) { - return false - } - - // compare the versions - for i := 0; i < len(currentVersionSlice); i++ { - c, _ := strconv.Atoi(currentVersionSlice[i]) - l, _ := strconv.Atoi(latestVersionSlice[i]) - - // if the current version is greater than the latest version - if c > l { - return false - } - // if the current version is less than the latest version - if c < l { - return true - } - } - - return false -} diff --git a/internal/app/update_test.go b/internal/app/update_test.go index 54110800d..5f2b57749 100644 --- a/internal/app/update_test.go +++ b/internal/app/update_test.go @@ -39,45 +39,3 @@ func Test_Update_FetchLastRelease(t *testing.T) { }) } } - -func Test_Update_ShouldUpdate(t *testing.T) { - tests := []struct { - name string - want bool - currentVersion string - latestVersion string - }{ - { - name: "Versions are equal, no update", - currentVersion: "v0.8.0", - latestVersion: "v0.8.0", - want: false, - }, - { - name: "Current version is higher than latest, no update", - currentVersion: "v0.10.0", - latestVersion: "v0.8.0", - want: false, - }, - { - name: "Current version's lenght is higher than latest, no update", - currentVersion: "v0.0.0.1", - latestVersion: "v0.0.0", - want: false, - }, - { - name: "Current version is lower than latest, update", - currentVersion: "v0.7.0", - latestVersion: "v0.8.0", - want: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := app.ShouldUpdate(tt.currentVersion, tt.latestVersion); got != tt.want { - t.Errorf("%s = got %v, want %v", tt.name, got, tt.want) - } - }) - } -} From 750e30385ffee3495a57bb5e75e26b28ce9148da Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 6 Oct 2022 16:25:28 +0200 Subject: [PATCH 066/383] chore: rework version update check using semver comparison --- cmd/root.go | 22 ++-- go.mod | 6 +- internal/semver/compare.go | 83 +++++++++++++-- internal/semver/compare_test.go | 176 ++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 21 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 3ddca52c2..56bc5e64c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,6 +16,7 @@ import ( "github.com/sighupio/furyctl/internal/app" "github.com/sighupio/furyctl/internal/cobrax" "github.com/sighupio/furyctl/internal/io" + "github.com/sighupio/furyctl/internal/semver" ) type rootConfig struct { @@ -73,14 +74,11 @@ Furyctl is a simple CLI tool to: analytics.Version(versions["version"]) analytics.Disable(cobrax.Flag[bool](cmd, "disable-analytics").(bool)) }, - PersistentPostRun: func(cmd *cobra.Command, _ []string) { + PersistentPostRun: func(_ *cobra.Command, _ []string) { // Show update message if available at the end of the command select { case release := <-r: - if release.Version == "unknown" { - return - } - if release.Version != versions["version"] { + if shouldUpgrade(release.Version, versions["version"]) { logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) } case err := <-e: @@ -107,10 +105,20 @@ Furyctl is a simple CLI tool to: return rootCmd } +func shouldUpgrade(releaseVersion, currentVersion string) bool { + if releaseVersion == "unknown" { + return false + } + + return semver.Gt(releaseVersion, currentVersion) +} + func checkUpdates(version string, rc chan app.Release, e chan error) { + defer close(rc) + defer close(e) + if version == "unknown" { rc <- app.Release{Version: version} - close(rc) return } @@ -121,6 +129,4 @@ func checkUpdates(version string, rc chan app.Release, e chan error) { } rc <- r - - close(rc) } diff --git a/go.mod b/go.mod index 636e32a7a..5571193fc 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -require gopkg.in/yaml.v3 v3.0.1 +require ( + github.com/google/go-cmp v0.5.8 + gopkg.in/yaml.v3 v3.0.1 +) require ( cloud.google.com/go v0.81.0 // indirect @@ -46,7 +49,6 @@ require ( github.com/gobuffalo/packd v1.0.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.8 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/internal/semver/compare.go b/internal/semver/compare.go index ddf67d0d6..2a06a043a 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -7,6 +7,7 @@ package semver import ( "fmt" "regexp" + "strconv" "strings" ) @@ -37,13 +38,10 @@ func SamePatch(a, b Version) bool { return true } - ap := strings.Split(a.String(), ".") - ma := fmt.Sprintf("%s.%s.%s", ap[0], ap[1], ap[2]) + aMajor, aMinor, aPatch, _ := Parts(a.String()) + bMajor, bMinor, bPatch, _ := Parts(b.String()) - bp := strings.Split(b.String(), ".") - mb := fmt.Sprintf("%s.%s.%s", bp[0], bp[1], bp[2]) - - return ma == mb + return aMajor == bMajor && aMinor == bMinor && aPatch == bPatch } // SameMinor takes two versions and tell if they are part of the same minor @@ -52,13 +50,76 @@ func SameMinor(a, b Version) bool { return true } - ap := strings.Split(a.String(), ".") - ma := fmt.Sprintf("%s.%s", ap[0], ap[1]) + aMajor, aMinor, _, _ := Parts(a.String()) + bMajor, bMinor, _, _ := Parts(b.String()) + + return aMajor == bMajor && aMinor == bMinor +} + +// Gt returns true if a is greater than b +func Gt(va, vb string) bool { + if va == vb { + return false + } + + aMajor, aMinor, aPatch, _ := Parts(va) + bMajor, bMinor, bPatch, _ := Parts(vb) + + if aMajor > bMajor { + return true + } + + if aMajor < bMajor { + return false + } + + if aMinor > bMinor { + return true + } + + if aMinor < bMinor { + return false + } + + if aPatch > bPatch { + return true + } + + if aPatch < bPatch { + return false + } + + return false +} + +// Parts returns the major, minor, patch and buil+prerelease parts of a version +func Parts(v string) (int, int, int, string) { + pv := EnsurePrefix(v, "v") + + if !isValid(pv) { + return 0, 0, 0, "" + } + + parts := strings.Split(EnsureNoPrefix(v, "v"), ".") - bp := strings.Split(b.String(), ".") - mb := fmt.Sprintf("%s.%s", bp[0], bp[1]) + ch := "-" + m := strings.Index(v, "-") + p := strings.Index(v, "+") + if (m == -1 && p > -1) || (m > -1 && p > -1 && p < m) { + ch = "+" + } + + patchParts := strings.Split(strings.Join(parts[2:], "."), ch) + + major, _ := strconv.Atoi(parts[0]) + minor, _ := strconv.Atoi(parts[1]) + patch, _ := strconv.Atoi(patchParts[0]) + + if len(patchParts) > 1 { + return major, minor, patch, strings.Join(patchParts[1:], ch) + } - return ma == mb + return major, minor, patch, "" } func isValid(v string) bool { diff --git a/internal/semver/compare_test.go b/internal/semver/compare_test.go index 3c0a1b97c..467fe6f27 100644 --- a/internal/semver/compare_test.go +++ b/internal/semver/compare_test.go @@ -7,6 +7,7 @@ package semver_test import ( "testing" + "github.com/google/go-cmp/cmp" "github.com/sighupio/furyctl/internal/semver" ) @@ -67,3 +68,178 @@ func Test_NewVersion(t *testing.T) { }) } } + +func TestGt(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + a string + b string + want bool + }{ + { + desc: "a equals b", + a: "0.1.0", + b: "0.1.0", + want: false, + }, + { + desc: "a lesser patch than b", + a: "0.1.0", + b: "0.1.1", + want: false, + }, + { + desc: "a greater patch than b", + a: "0.1.1", + b: "0.1.0", + want: true, + }, + { + desc: "a lesser minor than b", + a: "0.1.0", + b: "0.2.0", + want: false, + }, + { + desc: "a greater minor than b", + a: "0.2.0", + b: "0.1.0", + want: true, + }, + { + desc: "a lesser major than b", + a: "0.1.0", + b: "1.1.0", + want: false, + }, + { + desc: "a greater major than b", + a: "1.1.0", + b: "0.1.0", + want: true, + }, + { + desc: "a lesser major, greater minor and patch than b", + a: "0.2.2", + b: "1.0.0", + want: false, + }, + { + desc: "a lesser minor, greater patch than b", + a: "0.2.2", + b: "0.3.0", + want: false, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + if got := semver.Gt(tC.a, tC.b); got != tC.want { + t.Errorf("Gt() = %v, want %v", got, tC.want) + } + }) + } +} + +func TestParts(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + version string + want []any + }{ + {desc: "0.0.4", want: []any{0, 0, 4, ""}}, + {desc: "1.2.3", want: []any{1, 2, 3, ""}}, + {desc: "10.20.30", want: []any{10, 20, 30, ""}}, + {desc: "1.1.2-prerelease+meta", want: []any{1, 1, 2, "prerelease+meta"}}, + {desc: "1.1.2+meta", want: []any{1, 1, 2, "meta"}}, + {desc: "1.1.2+meta-valid", want: []any{1, 1, 2, "meta-valid"}}, + {desc: "1.0.0-alpha", want: []any{1, 0, 0, "alpha"}}, + {desc: "1.0.0-beta", want: []any{1, 0, 0, "beta"}}, + {desc: "1.0.0-alpha.beta", want: []any{1, 0, 0, "alpha.beta"}}, + {desc: "1.0.0-alpha.beta.1", want: []any{1, 0, 0, "alpha.beta.1"}}, + {desc: "1.0.0-alpha.1", want: []any{1, 0, 0, "alpha.1"}}, + {desc: "1.0.0-alpha0.valid", want: []any{1, 0, 0, "alpha0.valid"}}, + {desc: "1.0.0-alpha.0valid", want: []any{1, 0, 0, "alpha.0valid"}}, + {desc: "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", want: []any{1, 0, 0, "alpha-a.b-c-somethinglong+build.1-aef.1-its-okay"}}, + {desc: "1.0.0-rc.1+build.1", want: []any{1, 0, 0, "rc.1+build.1"}}, + {desc: "2.0.0-rc.1+build.123", want: []any{2, 0, 0, "rc.1+build.123"}}, + {desc: "1.2.3-beta", want: []any{1, 2, 3, "beta"}}, + {desc: "10.2.3-DEV-SNAPSHOT", want: []any{10, 2, 3, "DEV-SNAPSHOT"}}, + {desc: "1.2.3-SNAPSHOT-123", want: []any{1, 2, 3, "SNAPSHOT-123"}}, + {desc: "1.0.0", want: []any{1, 0, 0, ""}}, + {desc: "2.0.0", want: []any{2, 0, 0, ""}}, + {desc: "1.1.7", want: []any{1, 1, 7, ""}}, + {desc: "2.0.0+build.1848", want: []any{2, 0, 0, "build.1848"}}, + {desc: "2.0.1-alpha.1227", want: []any{2, 0, 1, "alpha.1227"}}, + {desc: "1.0.0-alpha+beta", want: []any{1, 0, 0, "alpha+beta"}}, + {desc: "1.2.3----RC-SNAPSHOT.12.9.1--.12+788", want: []any{1, 2, 3, "---RC-SNAPSHOT.12.9.1--.12+788"}}, + {desc: "1.2.3----R-S.12.9.1--.12+meta", want: []any{1, 2, 3, "---R-S.12.9.1--.12+meta"}}, + {desc: "1.2.3----RC-SNAPSHOT.12.9.1--.12", want: []any{1, 2, 3, "---RC-SNAPSHOT.12.9.1--.12"}}, + {desc: "1.0.0+0.build.1-rc.10000aaa-kk-0.1", want: []any{1, 0, 0, "0.build.1-rc.10000aaa-kk-0.1"}}, + {desc: "999999999999999999.999999999999999999.99999999999999999", want: []any{999999999999999999, 999999999999999999, 99999999999999999, ""}}, + {desc: "1.0.0-0A.is.legal", want: []any{1, 0, 0, "0A.is.legal"}}, + + {desc: "1", want: []any{0, 0, 0, ""}}, + {desc: "1.2", want: []any{0, 0, 0, ""}}, + {desc: "1.2.3-0123", want: []any{0, 0, 0, ""}}, + {desc: "1.2.3-0123.0123", want: []any{0, 0, 0, ""}}, + {desc: "1.1.2+.123", want: []any{0, 0, 0, ""}}, + {desc: "+invalid", want: []any{0, 0, 0, ""}}, + {desc: "-invalid", want: []any{0, 0, 0, ""}}, + {desc: "-invalid+invalid", want: []any{0, 0, 0, ""}}, + {desc: "-invalid.01", want: []any{0, 0, 0, ""}}, + {desc: "alpha", want: []any{0, 0, 0, ""}}, + {desc: "alpha.beta", want: []any{0, 0, 0, ""}}, + {desc: "alpha.beta.1", want: []any{0, 0, 0, ""}}, + {desc: "alpha.1", want: []any{0, 0, 0, ""}}, + {desc: "alpha+beta", want: []any{0, 0, 0, ""}}, + {desc: "alpha_beta", want: []any{0, 0, 0, ""}}, + {desc: "alpha.", want: []any{0, 0, 0, ""}}, + {desc: "alpha..", want: []any{0, 0, 0, ""}}, + {desc: "beta", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha_beta", want: []any{0, 0, 0, ""}}, + {desc: "-alpha.", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha..", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha..1", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha...1", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha....1", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha.....1", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha......1", want: []any{0, 0, 0, ""}}, + {desc: "1.0.0-alpha.......1", want: []any{0, 0, 0, ""}}, + {desc: "01.1.1", want: []any{0, 0, 0, ""}}, + {desc: "1.01.1", want: []any{0, 0, 0, ""}}, + {desc: "1.1.01", want: []any{0, 0, 0, ""}}, + {desc: "1.2", want: []any{0, 0, 0, ""}}, + {desc: "1.2.3.DEV", want: []any{0, 0, 0, ""}}, + {desc: "1.2-SNAPSHOT", want: []any{0, 0, 0, ""}}, + {desc: "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788", want: []any{0, 0, 0, ""}}, + {desc: "1.2-RC-SNAPSHOT", want: []any{0, 0, 0, ""}}, + {desc: "-1.0.3-gamma+b7718", want: []any{0, 0, 0, ""}}, + {desc: "+justmeta", want: []any{0, 0, 0, ""}}, + {desc: "9.8.7+meta+meta", want: []any{0, 0, 0, ""}}, + {desc: "9.8.7-whatever+meta+meta", want: []any{0, 0, 0, ""}}, + {desc: "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12", want: []any{0, 0, 0, ""}}, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + major, minor, patch, rest := semver.Parts(tC.desc) + + parts := []any{major, minor, patch, rest} + + if !cmp.Equal(parts, tC.want) { + t.Errorf("parts mismatch (-want +got):\n%s", cmp.Diff(tC.want, parts)) + } + }) + } +} From d128be5ef9d8c9cc24f1157051eafa522e50802b Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 6 Oct 2022 16:33:16 +0200 Subject: [PATCH 067/383] fix: linting issue --- internal/semver/compare_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/semver/compare_test.go b/internal/semver/compare_test.go index 467fe6f27..1bfad991c 100644 --- a/internal/semver/compare_test.go +++ b/internal/semver/compare_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/sighupio/furyctl/internal/semver" ) From 11156fa386888324b6f93fafdf48817191ebd585 Mon Sep 17 00:00:00 2001 From: omissis Date: Thu, 6 Oct 2022 17:00:44 +0200 Subject: [PATCH 068/383] fix: update test --- internal/app/update_test.go | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/internal/app/update_test.go b/internal/app/update_test.go index 5f2b57749..9562425ab 100644 --- a/internal/app/update_test.go +++ b/internal/app/update_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build integration + package app_test import ( @@ -11,31 +13,16 @@ import ( ) func Test_Update_FetchLastRelease(t *testing.T) { - tests := []struct { - name string - want app.Release - }{ - { - name: "test", - want: app.Release{ - URL: "https://github.com/sighupio/furyctl/releases/tag/v0.8.0", - Version: "v0.8.0", - }, - }, + got, err := app.GetLatestRelease() + if err != nil { + t.Fatal(err) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := app.GetLatestRelease() - if err != nil { - t.Fatal(err) - } - - t.Log(got) + if got.Version == "" { + t.Error("Version is empty") + } - if got.Version != tt.want.Version { - t.Errorf("FetchLastRelease() = %v, want %v", got, tt.want) - } - }) + if got.URL == "" { + t.Error("Version is empty") } } From ef8062f08b75b5cb4c4965fb4f480ba96322bfc7 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Fri, 14 Oct 2022 16:13:09 +0200 Subject: [PATCH 069/383] feat: new-logo (#104) * docs: update logo and improve logo placement --- README.md | 16 +- docs/assets/furyctl-logo.png | Bin 26566 -> 0 bytes docs/assets/furyctl-logo.svg | 368 +++++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+), 7 deletions(-) delete mode 100644 docs/assets/furyctl-logo.png create mode 100644 docs/assets/furyctl-logo.svg diff --git a/README.md b/README.md index 1f0a5ca1b..4dd660d0a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@

-
- Furyctl -

+ furyctl logo -

The multi-purpose command line tool for the Kubernetes Fury Distribution.

- +

The multi-purpose command line tool
for the Kubernetes Fury Distribution

[![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg)](https://ci.sighup.io/sighupio/furyctl) ![Release](https://img.shields.io/github/v/release/sighupio/furyctl?label=Furyctl) @@ -13,17 +10,22 @@ ![License](https://img.shields.io/github/license/sighupio/furyctl) [![Go Report Card](https://goreportcard.com/badge/github.com/sighupio/furyctl)](https://goreportcard.com/report/github.com/sighupio/furyctl) + + + -Furyctl is a simple CLI tool to: +Furyctl is a command line interface tool to: -- download and manage the Kubernetes Fury Distribution (KFD) modules - create and manage Fury clusters on AWS +- download and manage the Kubernetes Fury Distribution (KFD) modules
![Furyctl usage](docs/assets/furyctl.gif) +> 💡 Learn more about the Kubernetes Fury Distribution in the [official site](https://kubernetesfury.com). + ## Installation ### Installation from binaries diff --git a/docs/assets/furyctl-logo.png b/docs/assets/furyctl-logo.png deleted file mode 100644 index 2af7c941ff5f9676bdb66f577bf310829d247108..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26566 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 diff --git a/docs/assets/furyctl-logo.svg b/docs/assets/furyctl-logo.svg new file mode 100644 index 000000000..8de5eb8d1 --- /dev/null +++ b/docs/assets/furyctl-logo.svg @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d068873c4a4c1fb681e7d78c53f47cce5831f7b0 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 12 Dec 2022 12:45:19 +0100 Subject: [PATCH 070/383] fix: linting after rebase --- internal/configuration/config_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/configuration/config_test.go b/internal/configuration/config_test.go index ebb33daea..c8033074d 100644 --- a/internal/configuration/config_test.go +++ b/internal/configuration/config_test.go @@ -54,11 +54,11 @@ func init() { } sampleEKSConfig.Provisioner = "eks" sampleEKSConfig.Spec = clustercfg.EKS{ - Version: "1.23", - Network: "vpc-1", - SubNetworks: []string{"subnet-1", "subnet-2", "subnet-3"}, - DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"0.0.0.0/0"}}, - SSHPublicKey: "123", + Version: "1.23", + Network: "vpc-1", + SubNetworks: []string{"subnet-1", "subnet-2", "subnet-3"}, + DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"0.0.0.0/0"}}, + SSHPublicKey: "123", LogRetentionDays: 30, NodePools: []clustercfg.EKSNodePool{ { From a3d6eaa3f3118ab8698b4a520804bec7845ffa05 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 17 Jan 2023 17:24:15 +0100 Subject: [PATCH 071/383] Feature/create command (#97) * feat: infra code heavily WIP * feat: infra phase * fix: after rebase * chore: refactoring after rebase * fix: added license banner * chore: refactoring on eks cluster create cmd * fix: missing folder creation * fix: linting * feat: added dryrun * feat: moved furyfile inside struct in eks/clusters chore: removed deprecated func calls to ioutil Read/WriteFile * fix: post rebase * chore: changed order of download/validate * chore: first step of the distribution config rework * chore: update deps * feat: add config structs validation * fix: tests after introducing validation * chore: refactor pre-create cluster code * chore: refactor 'create cluster' logic to remove the need to use other app services * chore: rename variable * feat: first draft of kubernetes phase * fix: after rebase * fix: added experimental conf to terraform * feat: kubernetes phase is working * feat: taking vars from infra output * refactor: embed base into phase files * fix: go imports * fix: add missing license banners, temporarily deactivate some linting issues * chore: improve tools error messages * fix: bats integration test * chore: temporarily silenced a couple linting issues * feat: add create config command * feat: add short description for create config command * fix: rebase issue * fix: default distribution version * chore: add missing license banners * feat: replace bats tests with ginkgo ones, add workdir flag * feat: add test for download deps, refactor e2e tests * feat: tag all test files with build tag, separate integration test data from e2e's * feat: add test for create config command * chore: add missing license banners * chore: update deps * fix: validate deps test, refactor code to have cmds output for free * feat: bump golang in ci to 1.19.1 * feat: fix integration test data path * fix: restore netrc file in test pipeline steps * chore: fix create cluster command code style * chore: remove old code * chore: remove packr go library * chore: remove old deps from manifests * chore: bring back remaining logic from internal/app to cmd * chore: update deps * chore: refactor 'create cluster' logic to remove the need to use other app services * fix: unhandled nil on Walk function * chore: added provisioners dir to embed + refactor * chore: refactor eksCluster v1alpha2 to make the code more self-contained * chore: remove pointless use of generics * chore: further refactor of cluster creator factory * chore: refactor logic out of cluster creator phases to cmd runners * chore: refactor tools and ekscluster phases * chore: refactor 'x' packages within their own, dedicated folder * chore: refactor 'base' struct to 'phase' * feat: added expensive test on cluster create * fix: tests after rebase * feat: add local code coverage visualization * feat: refine test targets * feat: add manual confirmation in makefile before 'expensive' test targets * feat: add tools target to simplify development * chore: sort tools in makefile * chore: remove unused tools in makefile * fix: ansible bin not found * fix: e2e tests on CI * fix: wrong env var * feat: add tests * feat: add missing tool * chore: add more tests * fix: add semver's TesSameMinorStr and TestSamePatchStr * feat: merge furyctl and defaults in distribution phase * fix: linting * fix: bug on _helpers.tpl not found * fix: remove double code coverage report opening in makefile * chore: added test to NewDefaultModelFromStruct * chore: add metadata key to merged config * chore: upgrade to new schema * fix: test to reflect new furyctl file * feat: dostrobution terraform step wip * fix: distribution config file wrong paths * chore: removed technical debt * feat: added terraform step in phase distribution * feat: inject into terraform wip * chore: updated furyctl-defautls in tests * feat: WIP distro manifests * fix: post distro review * fix: updated from new distro * chore: refactoring + tests * fix: tests + bugs * chore: dump template updated * fix: e2e tests * Chore/enable linters (#105) * feat: first linters round * fix: linting after rebase * fix: unit tests * fix: nolint needed * fix: wrapcheck * chore: removed commented line from rules * fix: linting goerr113 * chore: added revive linter * fix: wrong args on kubectl Co-authored-by: Alessio Pragliola * feat: refactor to enable deletion (#112) * feat: refactor to enable deletion * feat: delete command * feat: phase in delete * feat: check pending resources / retry resource apply * fix: linting * chore: added log infos * feat: log to file and stdout * feat: delete ingresses * fix: tests + linting * feat: error thrown when cfg already exists (#116) * Feat: switch from current to home dir (#117) * feat: switch to home dir in cmd * feat: changed dir from current workdir to homedir * fix: e2e tests * feat: copy kubeconfig to current workdir * feat: added copy ovpn file + changed phase dirs to not hidden * feat: added --log flag (#119) * feat: added --log flag * fix: linting * fix: e2e testing * fix: wrong permission + improve flag description * Feature/download bin default path (#118) * feat: changed bin folder structure * fix: e2e testing * fix: unit tests * Apply suggestions from code review Co-authored-by: Claudio Beatrice * chore: removed misleading errors from cmds * fix: post change requests * fix: removed debug flag when not needed * Update internal/dependencies/tools/tool.go Co-authored-by: Claudio Beatrice * feat: added bin-path flag to commands that need it Co-authored-by: Claudio Beatrice * feat: add analytics tracker and events * chore: rename DistroDetails in ClusterDetails * chore: small refactor,remove vscode's config file * chore: improve command tracking * feat: add analytics enable/disable capability * fix: enable analytics flag * fix: rebase issues * chore: fix rebase issue * feat: huge refactor,add more analytics logic, add flush * chore: add suggested changes * fix: license banner * chore: optimize code * feat: add mixpanel token's secret * fix: rebase issues * fix: cluster's error message Co-authored-by: Claudio Beatrice * fix: download deps error message Co-authored-by: Claudio Beatrice * chore: disable analytics in e2e tests, ignore any logs * chore: replace fatal with error's level * chore: replace error with panic to handle the error * chore: Missing NL Co-authored-by: Claudio Beatrice * chore: change log level Co-authored-by: Claudio Beatrice * chore: rename GuardEvent to StopEvent * chore: refactor * feat: add cmdutil, wrap internal errors * fix: license banner * feat: add parsing flags to the cluster deletion * fix: wrong flag name * chore: update json schema validator from v1 to v5 * chore: update deps * chore: update code after deps upgrade * feat: improve missing binary deps error message * chore: small refactor * fix: miss go build test unit tag * feat: change retry message and change to debug log level * feat: implement move from hard-coded furyctl.yaml template to downloading it from the distro (#125) * feat: implement move from hard-coded furyctl.yaml template to downloading it from the distro * chore: rework distribution.GetTemplatePath function, fix tests * feat: make ginkgo tests output more verbose * chore: remove useless test file * chore: add test file result to gitignore * fix: update var references in phase templates * fix: add random suffix to keyprefix used to store tf state in tests * chore: typo in missing flag Co-authored-by: Alessio Pragliola * feat: skip phase flag added * feat: removed orphaned resources * feat: added more expensive tests * fix: missing NL at end of files * chore: refactor create cluster command to reduce cyclomatic complexity * chore: refine e2e tests descriptions * chore: fix linting issues * feat: add awscli's tool and runner * chore: adapt tools validation with the new kfd schema * chore: update with the new kfd schema, fix auto-connect flag * feat: update gitignore * chore: add new line at the end of file * chore: update with the new kfd changes * fix: remove unused methods implementations * chore: improve the logic * chore: format and fix lint issues * fix: broken tests * chore: point fury-distribution dep to the latest fury-next branch of the distro * fix: add missing aws fake binary, avoid double channel closing. * fix: add missing create cluster e2e test fake binaries * fix: make create cluster e2e test fake binaries executable * chore: try fixing e2e test * chore: revert experiment with openvpn base path * feat: add flag to skip deps validation, rename skip-download flag * chore: fix linting issues * chore: update deps * chore: add ellipsis to a string * chore: debug e2e test * chore: debug e2e test * chore: remove e2e tests debug * chore: re-enable all e2e tests again * feat: add aws-cli to e2e-test image in CI * chore: replace deprecated go methods in tests * chore: update ginkgo and gomega deps * feat: improve ginkgo tests output * fix: add missing dollar escaping in drone CI manifest * fix: move openvpn tool version check after infra folder setup during cluster creation * feat: use a tmp file for the patched furyctl.yaml instead of the original one * chore: restore WorkDir for furyagent runner in infra phase of cluster creation * feat: moved source from fury-distribution to templates/distribution * chore: bump fury version, update template folders, replace panic with log+exit * chore: removed ApiServerEndpointAccess from phase kubernetes * chore: updated fields from new schema * fix: wrap logic in main to correctly handle defers and process exit codes * chore: remove unused code * chore: bump deps * chore: update tests to match the new schema * fix: handle dry-run of cluster creation kubernetes phase gracefully * chore: handle optional values * chore: reverted removal of .Spec.Kubernetes.ApiServerEndpointAccess * chore: update test schemas * fix: wrong cidr range * fix: linting * chore: better error msg vpcID * chore: reformat code * chore: update fury-distribution dep * chore: handle apiServerEndpointAccess as a pointer * chore: update distro schemas * chore: refine node templates parsing to better handle missing vars detection * chore: update furyctl.yaml files in e2e tests to match latest schema * fix: unique fields in template's nodes parser + skip variable nodes * fix: TestVariableNode unit test * chore: update fury-distribution dep * fix: make help text for version flag in config create command a bit more clear * feat: introduce support for distribution relative paths in distro downloader * chore(gomod): clean up go sum * feat: refine deps validation command * fix: set the right env var name in info message * fix: improve help text a bit * fix: improve help text a bit * fix: linting issues and tests * fix: improve help text * chore: improve OpenVPN connection messages * Feature/first wave adjustments (#131) * feat: improved log messages task list: - add debug message when waiting for cluster resources removal - append '...' to Deleting x phase messages - when deleting a cluster or deleting just phase infrastructure print a WARN telling the user to kill the openvpn process * feat: bump golang version to 1.19.4 * feat: added kubeconfig flag * fix: linting * chore: refactor phases * fix: e2e testing in CI following workaround found on: https://github.com/sgerrand/alpine-pkg-glibc/issues/185 * fix: wrong order in apk add flags (?) * fix: e2e testing * feat: added mandatory condition to kubeconfig flag in create cmd * feat: added mandatory condition to kubeconfig flag in delete cmd * chore: moved up --kubeconfig validation * chore: better UX from Ramiro's review Co-authored-by: Ramiro Algozino * chore: UX improvement on delete cluster missing kubeconfig Co-authored-by: Ramiro Algozino * chore: improved missing kubeconfig msg + revert to warn * chore: adjust delete cluster missing kubeconfig flag msg Co-authored-by: Ramiro Algozino * feat: renamed bucket to bucketName (#146) * feat: removed furyctl binary version check (#147) * feat: handle unsupported phases (#149) * feat: add repo check * chore: add retries threshold, improve ux * chore: small refactor * fix: switch git prefix * fix: modules download not working on branches * fix: linting on deps download * feat: added dry run to cluster delete (#154) * feat: added dry run to infra/kube phase * feat: added dry run to distro phase * chore: added debug mode to exec in dry-run * feat: improved ux in log messages * chore: remove logrus timestamp format * chore: force debuglevel as workaround for now * fix: changed wrong url in help --distro-location * feat: added dry-run logic in phase distribution to create cluster cmd (#157) * feat: added dry-run logic to distribution phase in create cluster * chore: removed log message * chore: removed kubectl apply --dry-run * feat: store furyctl.yaml in a cluster's secret * feat: add missing license banner and an info message * chore: use creator kubeconfig path * chore: replace kube client with kubectl runner * chore: small refactor * fix: wrong config path * chore: refactor * fix: don't store secret if dryrun is enabled * chore: refactor dryRun injection * fix: problem with kubeconfig / secret file * chore: added unit tests + refactoring to kubex Co-authored-by: omissis Co-authored-by: Luigi <50343470+luigibarbato@users.noreply.github.com> Co-authored-by: Luigi Barbato Co-authored-by: Ramiro Algozino --- .drone.yml | 66 +- .gitignore | 15 +- .goreleaser.yml | 1 - .rules/.golangci.yml | 94 +- CONTRIBUTING.md | 171 -- Makefile | 57 +- README.md | 16 + cmd/completion.go | 57 +- cmd/create.go | 24 + cmd/create/cluster.go | 370 ++++ cmd/create/config.go | 176 ++ cmd/delete.go | 23 + cmd/delete/cluster.go | 257 +++ cmd/download.go | 5 +- cmd/download/dependencies.go | 99 +- cmd/dump.go | 5 +- cmd/dump/template.go | 101 +- cmd/root.go | 163 +- cmd/validate.go | 7 +- cmd/validate/config.go | 68 +- cmd/validate/dependencies.go | 104 +- cmd/version.go | 23 +- .../resources.go => configs/main.go | 7 +- configs/provisioners/bootstrap/aws/main.tf | 34 - configs/provisioners/cluster/eks/main.tf | 39 - configs/provisioners/cluster/eks/main.tf.tpl | 34 + configs/provisioners/cluster/eks/variables.tf | 2 +- go.mod | 135 +- go.sum | 467 ++--- internal/analytics/analytics.go | 167 -- internal/analytics/event.go | 98 + internal/analytics/tracker.go | 176 ++ .../kfd/v1alpha2/eks/create/distribution.go | 605 +++++++ .../kfd/v1alpha2/eks/create/infrastructure.go | 459 +++++ .../kfd/v1alpha2/eks/create/kubernetes.go | 620 +++++++ internal/apis/kfd/v1alpha2/eks/creator.go | 232 +++ .../kfd/v1alpha2/eks/delete/distribution.go | 392 ++++ .../kfd/v1alpha2/eks/delete/infrastructure.go | 80 + .../kfd/v1alpha2/eks/delete/kubernetes.go | 81 + internal/apis/kfd/v1alpha2/eks/deleter.go | 117 ++ internal/apis/kfd/v1alpha2/eks/init.go | 25 + internal/app/common_test.go | 153 -- internal/app/download_dependencies.go | 165 -- internal/app/download_dependencies_test.go | 112 -- internal/app/update.go | 19 +- internal/app/validate_config.go | 136 -- internal/app/validate_config_test.go | 196 -- internal/app/validate_dependencies.go | 313 ---- internal/app/validate_dependencies_test.go | 147 -- internal/bootstrap/bootstrap.go | 448 ----- internal/bootstrap/configuration/aws.go | 27 - .../bootstrap/provisioners/aws/provisioner.go | 278 --- internal/cluster/cluster.go | 472 ----- internal/cluster/configuration/common.go | 37 - internal/cluster/configuration/eks.go | 64 - internal/cluster/creator.go | 131 ++ internal/cluster/deleter.go | 94 + internal/cluster/phase.go | 166 ++ .../cluster/provisioners/eks/provisioner.go | 404 ----- internal/cmd/cmdutil/flag.go | 49 + internal/config/validate.go | 138 ++ .../assets/aws-bootstrap-file-state.yml | 34 - .../configuration/assets/aws-bootstrap.yml | 29 - internal/configuration/assets/eks-cluster.yml | 45 - .../configuration/assets/invalid-config.yml | 9 - .../configuration/assets/invalid-kind.yml | 9 - .../assets/invalid-provisioner-bootstrap.yml | 9 - internal/configuration/config.go | 129 -- internal/configuration/config_test.go | 163 -- internal/configuration/templates.go | 314 ---- internal/dependencies/download.go | 220 +++ internal/dependencies/envvars/validator.go | 52 + internal/dependencies/tools/ansible.go | 59 + internal/dependencies/tools/ansible_test.go | 91 + internal/dependencies/tools/awscli.go | 62 + internal/dependencies/tools/awscli_test.go | 75 + internal/dependencies/tools/furyagent.go | 75 + internal/dependencies/tools/furyagent_test.go | 134 ++ internal/dependencies/tools/kubectl.go | 68 + internal/dependencies/tools/kubectl_test.go | 131 ++ internal/dependencies/tools/kustomize.go | 67 + internal/dependencies/tools/kustomize_test.go | 134 ++ internal/dependencies/tools/openvpn.go | 60 + internal/dependencies/tools/openvpn_test.go | 95 + internal/dependencies/tools/terraform.go | 67 + internal/dependencies/tools/terraform_test.go | 134 ++ .../tools/test_data/aws-cli/2.8.12/aws | 5 + .../test_data/furyagent/0.3.0}/furyagent | 0 .../tools/test_data/furyagent/0.4.0/furyagent | 5 + .../tools/test_data/kubectl/1.21.1}/kubectl | 2 +- .../tools/test_data/kubectl/1.22.0/kubectl | 5 + .../test_data/kustomize/3.5.3}/kustomize | 2 +- .../tools/test_data/kustomize/3.9.4/kustomize | 5 + .../test_data/terraform/0.15.4}/terraform | 0 .../tools/test_data/terraform/1.3.0/terraform | 6 + internal/dependencies/tools/tool.go | 180 ++ internal/dependencies/tools/tool_test.go | 130 ++ internal/dependencies/tools/validator.go | 62 + internal/dependencies/tools/validator_test.go | 113 ++ internal/dependencies/validate.go | 44 + internal/distribution/download.go | 129 +- internal/distribution/download_test.go | 118 +- internal/distribution/model.go | 102 -- internal/distribution/path.go | 52 + internal/distribution/path_test.go | 174 ++ internal/execx/exec_test.go | 29 - internal/io/fs.go | 109 -- internal/merge/merge.go | 16 +- internal/merge/merge_test.go | 2 + internal/merge/model.go | 26 +- internal/merge/model_test.go | 49 + internal/project/project.go | 82 - internal/provisioners/provisioners.go | 72 - internal/schema/santhosh/loader.go | 2 +- internal/schema/santhosh/loader_test.go | 78 + internal/semver/compare.go | 42 +- internal/semver/compare_test.go | 110 ++ internal/semver/{prefix.go => process.go} | 14 +- internal/semver/process_test.go | 115 ++ internal/template/config.go | 134 ++ internal/template/config_test.go | 142 ++ internal/template/funcmap.go | 29 +- internal/template/funcmap_test.go | 91 + internal/template/generator.go | 81 +- internal/template/generator_test.go | 31 + internal/template/mapper/mapper.go | 16 +- internal/template/mapper/mapper_test.go | 2 + internal/template/model.go | 84 +- internal/template/model_test.go | 2 + internal/template/node.go | 48 +- internal/template/node_test.go | 6 +- internal/terraform/install.go | 132 -- internal/terraform/terraform.go | 162 -- internal/test/os.go | 21 + internal/tool/ansible/runner.go | 45 + internal/tool/ansible/runner_test.go | 58 + internal/tool/awscli/runner.go | 45 + internal/tool/awscli/runner_test.go | 58 + internal/tool/furyagent/runner.go | 69 + internal/tool/furyagent/runner_test.go | 82 + internal/tool/kubectl/runner.go | 165 ++ internal/tool/kubectl/runner_test.go | 58 + internal/tool/kustomize/runner.go | 60 + internal/tool/kustomize/runner_test.go | 58 + internal/tool/openvpn/runner.go | 58 + internal/tool/openvpn/runner_test.go | 71 + internal/tool/runner.go | 103 ++ internal/tool/runner_test.go | 94 + internal/tool/terraform/runner.go | 165 ++ internal/tool/terraform/runner_test.go | 147 ++ internal/tools/furyagent.go | 44 - internal/tools/furyagent_test.go | 70 - internal/tools/kubectl.go | 39 - internal/tools/kubectl_test.go | 67 - internal/tools/kustomize.go | 40 - internal/tools/kustomize_test.go | 70 - internal/tools/terraform.go | 40 - internal/tools/terraform_test.go | 70 - internal/tools/tool.go | 32 - internal/tools/tool_test.go | 51 - internal/x/cobra/cmd.go | 20 + internal/{cobrax => x/cobra}/flags.go | 0 internal/{cobrax => x/cobra}/flags_test.go | 4 +- internal/x/exec/cmd.go | 153 ++ internal/x/exec/cmd_test.go | 206 +++ .../{execx/exec.go => x/exec/executor.go} | 4 +- internal/x/exec/executor_test.go | 72 + internal/x/io/fs.go | 187 ++ internal/x/io/fs_test.go | 428 +++++ internal/{ => x}/io/nullwriter.go | 4 +- internal/x/io/nullwriter_test.go | 26 + internal/x/kube/config.go | 61 + internal/x/kube/config_test.go | 128 ++ internal/x/kube/secret.go | 40 + internal/x/kube/secret_test.go | 53 + internal/x/logrus/config.go | 79 + internal/x/map/build.go | 96 + internal/x/map/build_test.go | 157 ++ internal/{netx => x/net}/client.go | 0 internal/{netx => x/net}/hashicorp.go | 24 +- internal/{netx => x/net}/hashicorp_test.go | 16 +- internal/{osx => x/os}/path.go | 3 +- internal/x/os/path_test.go | 70 + internal/x/slices/uniq.go | 19 + internal/x/slices/uniq_test.go | 76 + internal/x/yaml/yaml.go | 61 + internal/x/yaml/yaml_test.go | 80 + internal/yaml/yaml.go | 48 - internal/yaml/yaml_test.go | 49 - main.go | 46 +- test/data/e2e/create/cluster/bin_mock/aws | 5 + .../e2e/create/cluster/bin_mock/furyagent | 5 + test/data/e2e/create/cluster/bin_mock/kubectl | 5 + .../e2e/create/cluster/bin_mock/kustomize | 5 + test/data/e2e/create/cluster/bin_mock/openvpn | 9 + .../e2e/create/cluster/bin_mock/terraform | 6 + .../cluster/data}/furyctl-defaults.yaml | 77 +- .../data/e2e/create/cluster/data/furyctl.yaml | 206 +++ .../e2e/create/cluster/data}/kfd.yaml | 25 +- .../schemas/ekscluster-kfd-v1alpha2.json | 459 +++-- .../config/default/data/expected-furyctl.yaml | 250 +++ .../e2e/create/config/distro}/kfd.yaml | 25 +- .../config/ekscluster-kfd-v1alpha2.yaml.tpl | 250 +++ .../v1.24.1/distro}/furyctl-defaults.yaml | 77 +- .../dependencies/v1.24.1/distro}/kfd.yaml | 25 +- .../schemas/ekscluster-kfd-v1alpha2.json | 1569 +++++++++++++++++ .../dependencies/v1.24.1}/furyctl.yaml | 8 +- .../data/expected-kustomization.yaml | 2 - .../complex-dry-run/furyctl-defaults.yaml} | 4 +- .../template}/complex-dry-run/furyctl.yaml | 29 +- .../distribution}/config/example.yaml | 0 .../distribution/kustomization.yaml.tpl | 28 + .../complex}/data/expected-kustomization.yaml | 0 .../template/complex/furyctl-defaults.yaml} | 4 +- .../e2e/dump/template}/complex/furyctl.yaml | 24 +- .../distribution}/config/example.yaml | 0 .../distribution/kustomization.yaml.tpl | 28 + .../furyctl-defaults.yaml} | 0 .../furyctl.yaml | 0 .../templates/distribution/file.txt.tpl | 1 + .../templates/distribution}/keepfile.txt | 0 .../templates/distribution}/file.txt.tpl | 0 .../template}/no-distribution-yaml/.gitkeep | 0 .../no-furyctl-yaml/furyctl-defaults.yaml} | 0 .../simple-dry-run/furyctl-defaults.yaml} | 0 .../template}/simple-dry-run/furyctl.yaml | 0 .../templates/distribution/file.txt.tpl | 1 + .../templates/distribution}/keepfile.txt | 0 .../template/simple/furyctl-defaults.yaml} | 0 .../e2e/dump/template}/simple/furyctl.yaml | 0 .../templates/distribution/file.txt.tpl | 1 + .../templates/distribution}/keepfile.txt | 0 .../config/correct}/furyctl-defaults.yaml | 77 +- .../e2e/validate/config/correct/furyctl.yaml | 206 +++ .../validate/config/correct}/kfd.yaml | 25 +- .../schemas/ekscluster-kfd-v1alpha2.json | 1569 +++++++++++++++++ .../validate/config/nodistro}/furyctl.yaml | 138 +- .../config/wrong/furyctl-defaults.yaml | 223 +++ .../e2e/validate/config/wrong}/furyctl.yaml | 132 +- test/data/e2e/validate/config/wrong/kfd.yaml | 35 + .../schemas/ekscluster-kfd-v1alpha2.json | 1569 +++++++++++++++++ .../correct/furyagent/0.3.0/furyagent | 5 + .../dependencies/correct/furyctl.yaml | 324 ++++ .../validate/dependencies/correct/kfd.yaml | 35 + .../correct/kubectl/1.24.9/kubectl | 5 + .../correct/kustomize/3.5.3/kustomize | 5 + .../correct/terraform/0.15.4/terraform | 6 + .../dependencies/missing/furyctl.yaml | 324 ++++ .../validate/dependencies/missing/kfd.yaml | 35 + .../wrong/furyagent/0.3.0}/furyagent | 0 .../validate/dependencies/wrong}/furyctl.yaml | 12 +- .../e2e/validate/dependencies/wrong/kfd.yaml | 35 + .../wrong/kubectl/1.24.9}/kubectl | 2 +- .../wrong/kustomize/3.5.3/kustomize | 5 + .../wrong/terraform/0.15.4}/terraform | 0 .../common/data/furyctl-defaults.yaml | 208 +++ test/data/expensive/common/data/kfd.yaml | 35 + .../data/schemas/ekscluster-kfd-v1alpha2.json | 1569 +++++++++++++++++ .../templates/distribution/.gitignore.tpl | 1 + .../data/templates/distribution/_helpers.tpl | 91 + .../manifests/auth/kustomization.yaml.tpl | 42 + .../auth/patches/infra-nodes.yml.tpl | 50 + .../auth/patches/pomerium-ingress.yml.tpl | 26 + .../auth/resources/ingress-infra.yml.tpl | 27 + .../auth/resources/pomerium-config.env.tpl | 31 + .../auth/resources/pomerium-policy.yml.tpl | 3 + .../manifests/auth/secrets/basic-auth.yml.tpl | 16 + .../manifests/auth/secrets/dex.yml.tpl | 36 + .../manifests/auth/secrets/pomerium.env.tpl | 8 + .../manifests/aws/kustomization.yaml.tpl | 18 + .../aws/patches/cluster-autoscaler.yml.tpl | 24 + .../aws/patches/ebs-csi-driver.yml.tpl | 7 + .../manifests/aws/patches/infra-nodes.yml.tpl | 74 + .../patches/load-balancer-controller.yml.tpl | 22 + .../manifests/aws/resources/sc.yml | 30 + .../manifests/dr/kustomization.yaml.tpl | 13 + .../manifests/dr/patches/infra-nodes.yml.tpl | 35 + .../manifests/dr/patches/velero.yml.tpl | 26 + .../velero-backupstoragelocation.yml.tpl | 12 + .../velero-volumesnapshotlocation.yml.tpl | 10 + .../manifests/ingress/kustomization.yaml.tpl | 79 + .../ingress/patches/cert-manager.yml.tpl | 10 + .../ingress/patches/external-dns.yml.tpl | 90 + .../ingress/patches/infra-nodes.yml.tpl | 161 ++ .../patches/ingress-nginx-external.yml.tpl | 24 + .../patches/ingress-nginx-internal.yml.tpl | 23 + .../ingress/patches/ingress-nginx.yml.tpl | 12 + .../ingress/patchesJson/ingress-nginx.yml.tpl | 5 + .../cert-manager-clusterissuer.yml.tpl | 24 + .../ingress/resources/ingress-infra.yml.tpl | 26 + .../manifests/ingress/secrets/.gitignore | 1 + .../manifests/ingress/secrets/tls.yml.tpl | 13 + .../manifests/kustomization.yaml.tpl | 17 + .../manifests/logging/kustomization.yaml.tpl | 40 + .../patches/events-index-template.json.tpl | 10 + .../logging/patches/infra-nodes.yml.tpl | 85 + ...ingress-controller-index-template.json.tpl | 10 + .../kubernetes-index-template.json.tpl | 10 + .../logging/patches/opensearch.yml.tpl | 24 + .../patches/systemd-index-template.json.tpl | 10 + .../logging/resources/ingress-infra.yml.tpl | 78 + .../monitoring/kustomization.yaml.tpl | 25 + .../patches/alertmanager-operated.yml.tpl | 8 + .../monitoring/patches/infra-nodes.yml.tpl | 109 ++ .../patches/prometheus-operated.yml.tpl | 24 + .../resources/ingress-infra.yml.tpl | 78 + .../monitoring/secrets/alertmanager.yml.tpl | 28 + .../networking/kustomization.yaml.tpl | 7 + .../manifests/opa/kustomization.yaml.tpl | 23 + .../gatekeeper-whitelist-namespace.yml.tpl | 8 + .../manifests/opa/patches/infra-nodes.yml.tpl | 48 + .../opa/resources/ingress-infra.yml.tpl | 26 + .../terraform/cert-manager.tf.tpl | 12 + .../terraform/cluster-autoscaler.tf.tpl | 9 + .../terraform/ebs-csi-driver.tf.tpl | 8 + .../terraform/external-dns.tf.tpl | 43 + .../terraform/load-balancer-controller.tf.tpl | 9 + .../distribution/terraform/main.tf.tpl | 15 + .../distribution/terraform/route53.tf.tpl | 59 + .../distribution/terraform/velero.tf.tpl | 9 + .../create-complete/data/furyctl.yaml | 129 ++ .../create-skip-infra/data/furyctl.yaml | 129 ++ .../create-skip-kube/data/furyctl.yaml | 129 ++ .../schema/santhosh/test-cluster-broken.json | 30 + .../schema/santhosh/test-cluster-correct.json | 31 + .../schema/santhosh/test-cluster-wrong.json | 16 + .../v1.24.1/distro/furyctl-defaults.yaml | 223 +++ test/data/integration/v1.24.1/distro/kfd.yaml | 35 + .../schemas/ekscluster-kfd-v1alpha2.json | 1569 +++++++++++++++++ test/data/integration/v1.24.1/furyctl.yaml | 244 +++ test/e2e/furyctl_test.go | 618 +++++++ test/expensive/furyctl_test.go | 325 ++++ test/helper.bash | 24 - test/integration/template-engine/.gitignore | 1 - .../source/kustomization.yaml.tpl | 28 - .../complex/source/kustomization.yaml.tpl | 28 - .../source/file.txt.tpl | 1 - .../data/simple-dry-run/source/file.txt.tpl | 1 - .../data/simple/source/file.txt.tpl | 1 - test/integration/template-engine/tests.sh | 208 --- .../schemas/ekscluster-kfd-v1alpha2.json | 1314 -------------- .../schemas/ekscluster-kfd-v1alpha2.json | 1314 -------------- .../data/dependencies-correct/ansible | 13 - .../data/dependencies-correct/kustomize | 5 - .../data/dependencies-missing/furyctl.yaml | 328 ---- .../data/dependencies-missing/kfd.yaml | 26 - .../data/dependencies-wrong/ansible | 13 - .../data/dependencies-wrong/furyctl.yaml | 328 ---- .../data/dependencies-wrong/kfd.yaml | 26 - test/integration/validation-cmd/tests.sh | 151 -- 350 files changed, 26815 insertions(+), 10498 deletions(-) delete mode 100644 CONTRIBUTING.md create mode 100644 cmd/create.go create mode 100644 cmd/create/cluster.go create mode 100644 cmd/create/config.go create mode 100644 cmd/delete.go create mode 100644 cmd/delete/cluster.go rename internal/distribution/resources.go => configs/main.go (69%) delete mode 100644 configs/provisioners/bootstrap/aws/main.tf delete mode 100644 configs/provisioners/cluster/eks/main.tf create mode 100644 configs/provisioners/cluster/eks/main.tf.tpl delete mode 100644 internal/analytics/analytics.go create mode 100644 internal/analytics/event.go create mode 100644 internal/analytics/tracker.go create mode 100644 internal/apis/kfd/v1alpha2/eks/create/distribution.go create mode 100644 internal/apis/kfd/v1alpha2/eks/create/infrastructure.go create mode 100644 internal/apis/kfd/v1alpha2/eks/create/kubernetes.go create mode 100644 internal/apis/kfd/v1alpha2/eks/creator.go create mode 100644 internal/apis/kfd/v1alpha2/eks/delete/distribution.go create mode 100644 internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go create mode 100644 internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go create mode 100644 internal/apis/kfd/v1alpha2/eks/deleter.go create mode 100644 internal/apis/kfd/v1alpha2/eks/init.go delete mode 100644 internal/app/common_test.go delete mode 100644 internal/app/download_dependencies.go delete mode 100644 internal/app/download_dependencies_test.go delete mode 100644 internal/app/validate_config.go delete mode 100644 internal/app/validate_config_test.go delete mode 100644 internal/app/validate_dependencies.go delete mode 100644 internal/app/validate_dependencies_test.go delete mode 100644 internal/bootstrap/bootstrap.go delete mode 100644 internal/bootstrap/configuration/aws.go delete mode 100644 internal/bootstrap/provisioners/aws/provisioner.go delete mode 100644 internal/cluster/cluster.go delete mode 100644 internal/cluster/configuration/common.go delete mode 100644 internal/cluster/configuration/eks.go create mode 100644 internal/cluster/creator.go create mode 100644 internal/cluster/deleter.go create mode 100644 internal/cluster/phase.go delete mode 100644 internal/cluster/provisioners/eks/provisioner.go create mode 100644 internal/cmd/cmdutil/flag.go create mode 100644 internal/config/validate.go delete mode 100644 internal/configuration/assets/aws-bootstrap-file-state.yml delete mode 100644 internal/configuration/assets/aws-bootstrap.yml delete mode 100644 internal/configuration/assets/eks-cluster.yml delete mode 100644 internal/configuration/assets/invalid-config.yml delete mode 100644 internal/configuration/assets/invalid-kind.yml delete mode 100644 internal/configuration/assets/invalid-provisioner-bootstrap.yml delete mode 100644 internal/configuration/config.go delete mode 100644 internal/configuration/config_test.go delete mode 100644 internal/configuration/templates.go create mode 100644 internal/dependencies/download.go create mode 100644 internal/dependencies/envvars/validator.go create mode 100644 internal/dependencies/tools/ansible.go create mode 100644 internal/dependencies/tools/ansible_test.go create mode 100644 internal/dependencies/tools/awscli.go create mode 100644 internal/dependencies/tools/awscli_test.go create mode 100644 internal/dependencies/tools/furyagent.go create mode 100644 internal/dependencies/tools/furyagent_test.go create mode 100644 internal/dependencies/tools/kubectl.go create mode 100644 internal/dependencies/tools/kubectl_test.go create mode 100644 internal/dependencies/tools/kustomize.go create mode 100644 internal/dependencies/tools/kustomize_test.go create mode 100644 internal/dependencies/tools/openvpn.go create mode 100644 internal/dependencies/tools/openvpn_test.go create mode 100644 internal/dependencies/tools/terraform.go create mode 100644 internal/dependencies/tools/terraform_test.go create mode 100755 internal/dependencies/tools/test_data/aws-cli/2.8.12/aws rename {test/integration/validation-cmd/data/dependencies-correct => internal/dependencies/tools/test_data/furyagent/0.3.0}/furyagent (100%) create mode 100755 internal/dependencies/tools/test_data/furyagent/0.4.0/furyagent rename {test/integration/validation-cmd/data/dependencies-correct => internal/dependencies/tools/test_data/kubectl/1.21.1}/kubectl (52%) create mode 100755 internal/dependencies/tools/test_data/kubectl/1.22.0/kubectl rename {test/integration/validation-cmd/data/dependencies-wrong => internal/dependencies/tools/test_data/kustomize/3.5.3}/kustomize (59%) create mode 100755 internal/dependencies/tools/test_data/kustomize/3.9.4/kustomize rename {test/integration/validation-cmd/data/dependencies-correct => internal/dependencies/tools/test_data/terraform/0.15.4}/terraform (100%) create mode 100755 internal/dependencies/tools/test_data/terraform/1.3.0/terraform create mode 100644 internal/dependencies/tools/tool.go create mode 100644 internal/dependencies/tools/tool_test.go create mode 100644 internal/dependencies/tools/validator.go create mode 100644 internal/dependencies/tools/validator_test.go create mode 100644 internal/dependencies/validate.go delete mode 100644 internal/distribution/model.go create mode 100644 internal/distribution/path.go create mode 100644 internal/distribution/path_test.go delete mode 100644 internal/execx/exec_test.go delete mode 100644 internal/io/fs.go delete mode 100644 internal/project/project.go delete mode 100644 internal/provisioners/provisioners.go create mode 100644 internal/schema/santhosh/loader_test.go rename internal/semver/{prefix.go => process.go} (63%) create mode 100644 internal/semver/process_test.go create mode 100644 internal/template/config_test.go delete mode 100644 internal/terraform/install.go delete mode 100644 internal/terraform/terraform.go create mode 100644 internal/test/os.go create mode 100644 internal/tool/ansible/runner.go create mode 100644 internal/tool/ansible/runner_test.go create mode 100644 internal/tool/awscli/runner.go create mode 100644 internal/tool/awscli/runner_test.go create mode 100644 internal/tool/furyagent/runner.go create mode 100644 internal/tool/furyagent/runner_test.go create mode 100644 internal/tool/kubectl/runner.go create mode 100644 internal/tool/kubectl/runner_test.go create mode 100644 internal/tool/kustomize/runner.go create mode 100644 internal/tool/kustomize/runner_test.go create mode 100644 internal/tool/openvpn/runner.go create mode 100644 internal/tool/openvpn/runner_test.go create mode 100644 internal/tool/runner.go create mode 100644 internal/tool/runner_test.go create mode 100644 internal/tool/terraform/runner.go create mode 100644 internal/tool/terraform/runner_test.go delete mode 100644 internal/tools/furyagent.go delete mode 100644 internal/tools/furyagent_test.go delete mode 100644 internal/tools/kubectl.go delete mode 100644 internal/tools/kubectl_test.go delete mode 100644 internal/tools/kustomize.go delete mode 100644 internal/tools/kustomize_test.go delete mode 100644 internal/tools/terraform.go delete mode 100644 internal/tools/terraform_test.go delete mode 100644 internal/tools/tool.go delete mode 100644 internal/tools/tool_test.go create mode 100644 internal/x/cobra/cmd.go rename internal/{cobrax => x/cobra}/flags.go (100%) rename internal/{cobrax => x/cobra}/flags_test.go (98%) create mode 100644 internal/x/exec/cmd.go create mode 100644 internal/x/exec/cmd_test.go rename internal/{execx/exec.go => x/exec/executor.go} (82%) create mode 100644 internal/x/exec/executor_test.go create mode 100644 internal/x/io/fs.go create mode 100644 internal/x/io/fs_test.go rename internal/{ => x}/io/nullwriter.go (79%) create mode 100644 internal/x/io/nullwriter_test.go create mode 100644 internal/x/kube/config.go create mode 100644 internal/x/kube/config_test.go create mode 100644 internal/x/kube/secret.go create mode 100644 internal/x/kube/secret_test.go create mode 100644 internal/x/logrus/config.go create mode 100644 internal/x/map/build.go create mode 100644 internal/x/map/build_test.go rename internal/{netx => x/net}/client.go (100%) rename internal/{netx => x/net}/hashicorp.go (66%) rename internal/{netx => x/net}/hashicorp_test.go (87%) rename internal/{osx => x/os}/path.go (85%) create mode 100644 internal/x/os/path_test.go create mode 100644 internal/x/slices/uniq.go create mode 100644 internal/x/slices/uniq_test.go create mode 100644 internal/x/yaml/yaml.go create mode 100644 internal/x/yaml/yaml_test.go delete mode 100644 internal/yaml/yaml.go delete mode 100644 internal/yaml/yaml_test.go create mode 100755 test/data/e2e/create/cluster/bin_mock/aws create mode 100755 test/data/e2e/create/cluster/bin_mock/furyagent create mode 100755 test/data/e2e/create/cluster/bin_mock/kubectl create mode 100755 test/data/e2e/create/cluster/bin_mock/kustomize create mode 100755 test/data/e2e/create/cluster/bin_mock/openvpn create mode 100755 test/data/e2e/create/cluster/bin_mock/terraform rename test/{integration/validation-cmd/data/config-valid-furyctl-yaml => data/e2e/create/cluster/data}/furyctl-defaults.yaml (74%) create mode 100644 test/data/e2e/create/cluster/data/furyctl.yaml rename test/{integration/validation-cmd/data/config-invalid-furyctl-yaml => data/e2e/create/cluster/data}/kfd.yaml (57%) rename test/data/{v1.23.3/distro => e2e/create/cluster/data}/schemas/ekscluster-kfd-v1alpha2.json (77%) create mode 100644 test/data/e2e/create/config/default/data/expected-furyctl.yaml rename test/{integration/validation-cmd/data/dependencies-correct => data/e2e/create/config/distro}/kfd.yaml (57%) create mode 100644 test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl rename test/{integration/validation-cmd/data/config-invalid-furyctl-yaml => data/e2e/download/dependencies/v1.24.1/distro}/furyctl-defaults.yaml (74%) rename test/{integration/validation-cmd/data/config-valid-furyctl-yaml => data/e2e/download/dependencies/v1.24.1/distro}/kfd.yaml (57%) create mode 100644 test/data/e2e/download/dependencies/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json rename test/data/{v1.23.3 => e2e/download/dependencies/v1.24.1}/furyctl.yaml (98%) rename test/{integration/template-engine/data/complex => data/e2e/dump/template/complex-dry-run}/data/expected-kustomization.yaml (85%) rename test/{integration/template-engine/data/complex/distribution.yaml => data/e2e/dump/template/complex-dry-run/furyctl-defaults.yaml} (99%) rename test/{integration/template-engine/data => data/e2e/dump/template}/complex-dry-run/furyctl.yaml (94%) rename test/{integration/template-engine/data/complex-dry-run/source => data/e2e/dump/template/complex-dry-run/templates/distribution}/config/example.yaml (100%) create mode 100644 test/data/e2e/dump/template/complex-dry-run/templates/distribution/kustomization.yaml.tpl rename test/{integration/template-engine/data/complex-dry-run => data/e2e/dump/template/complex}/data/expected-kustomization.yaml (100%) rename test/{integration/template-engine/data/complex-dry-run/distribution.yaml => data/e2e/dump/template/complex/furyctl-defaults.yaml} (99%) rename test/{integration/template-engine/data => data/e2e/dump/template}/complex/furyctl.yaml (94%) rename test/{integration/template-engine/data/complex/source => data/e2e/dump/template/complex/templates/distribution}/config/example.yaml (100%) create mode 100644 test/data/e2e/dump/template/complex/templates/distribution/kustomization.yaml.tpl rename test/{integration/template-engine/data/distribution-yaml-no-data-property/distribution.yaml => data/e2e/dump/template/distribution-yaml-no-data-property/furyctl-defaults.yaml} (100%) rename test/{integration/template-engine/data => data/e2e/dump/template}/distribution-yaml-no-data-property/furyctl.yaml (100%) create mode 100644 test/data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution/file.txt.tpl rename test/{integration/template-engine/data/distribution-yaml-no-data-property/source => data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution}/keepfile.txt (100%) rename test/{integration/template-engine/data/empty/source => data/e2e/dump/template/empty/templates/distribution}/file.txt.tpl (100%) rename test/{integration/template-engine/data => data/e2e/dump/template}/no-distribution-yaml/.gitkeep (100%) rename test/{integration/template-engine/data/no-furyctl-yaml/distribution.yaml => data/e2e/dump/template/no-furyctl-yaml/furyctl-defaults.yaml} (100%) rename test/{integration/template-engine/data/simple-dry-run/distribution.yaml => data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml} (100%) rename test/{integration/template-engine/data => data/e2e/dump/template}/simple-dry-run/furyctl.yaml (100%) create mode 100644 test/data/e2e/dump/template/simple-dry-run/templates/distribution/file.txt.tpl rename test/{integration/template-engine/data/simple-dry-run/source => data/e2e/dump/template/simple-dry-run/templates/distribution}/keepfile.txt (100%) rename test/{integration/template-engine/data/simple/distribution.yaml => data/e2e/dump/template/simple/furyctl-defaults.yaml} (100%) rename test/{integration/template-engine/data => data/e2e/dump/template}/simple/furyctl.yaml (100%) create mode 100644 test/data/e2e/dump/template/simple/templates/distribution/file.txt.tpl rename test/{integration/template-engine/data/simple/source => data/e2e/dump/template/simple/templates/distribution}/keepfile.txt (100%) rename test/data/{v1.23.3/distro => e2e/validate/config/correct}/furyctl-defaults.yaml (74%) create mode 100644 test/data/e2e/validate/config/correct/furyctl.yaml rename test/data/{v1.23.3/distro => e2e/validate/config/correct}/kfd.yaml (57%) create mode 100644 test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json rename test/{integration/validation-cmd/data/config-valid-furyctl-yaml => data/e2e/validate/config/nodistro}/furyctl.yaml (76%) create mode 100644 test/data/e2e/validate/config/wrong/furyctl-defaults.yaml rename test/{integration/validation-cmd/data/config-invalid-furyctl-yaml => data/e2e/validate/config/wrong}/furyctl.yaml (77%) create mode 100644 test/data/e2e/validate/config/wrong/kfd.yaml create mode 100644 test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json create mode 100755 test/data/e2e/validate/dependencies/correct/furyagent/0.3.0/furyagent create mode 100644 test/data/e2e/validate/dependencies/correct/furyctl.yaml create mode 100644 test/data/e2e/validate/dependencies/correct/kfd.yaml create mode 100755 test/data/e2e/validate/dependencies/correct/kubectl/1.24.9/kubectl create mode 100755 test/data/e2e/validate/dependencies/correct/kustomize/3.5.3/kustomize create mode 100755 test/data/e2e/validate/dependencies/correct/terraform/0.15.4/terraform create mode 100644 test/data/e2e/validate/dependencies/missing/furyctl.yaml create mode 100644 test/data/e2e/validate/dependencies/missing/kfd.yaml rename test/{integration/validation-cmd/data/dependencies-wrong => data/e2e/validate/dependencies/wrong/furyagent/0.3.0}/furyagent (100%) rename test/{integration/validation-cmd/data/dependencies-correct => data/e2e/validate/dependencies/wrong}/furyctl.yaml (97%) create mode 100644 test/data/e2e/validate/dependencies/wrong/kfd.yaml rename test/{integration/validation-cmd/data/dependencies-wrong => data/e2e/validate/dependencies/wrong/kubectl/1.24.9}/kubectl (76%) create mode 100755 test/data/e2e/validate/dependencies/wrong/kustomize/3.5.3/kustomize rename test/{integration/validation-cmd/data/dependencies-wrong => data/e2e/validate/dependencies/wrong/terraform/0.15.4}/terraform (100%) create mode 100644 test/data/expensive/common/data/furyctl-defaults.yaml create mode 100644 test/data/expensive/common/data/kfd.yaml create mode 100644 test/data/expensive/common/data/schemas/ekscluster-kfd-v1alpha2.json create mode 100644 test/data/expensive/common/data/templates/distribution/.gitignore.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/_helpers.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/patches/pomerium-ingress.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/resources/ingress-infra.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-config.env.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-policy.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/basic-auth.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/dex.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/pomerium.env.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/aws/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/aws/patches/cluster-autoscaler.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/aws/patches/ebs-csi-driver.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/aws/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/aws/patches/load-balancer-controller.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/aws/resources/sc.yml create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/dr/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/dr/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/dr/patches/velero.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-backupstoragelocation.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-volumesnapshotlocation.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/cert-manager.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/external-dns.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-external.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-internal.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/patchesJson/ingress-nginx.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/cert-manager-clusterissuer.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/ingress-infra.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/.gitignore create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/tls.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/patches/events-index-template.json.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/patches/ingress-controller-index-template.json.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/patches/kubernetes-index-template.json.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/patches/opensearch.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/patches/systemd-index-template.json.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/logging/resources/ingress-infra.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/monitoring/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/alertmanager-operated.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/prometheus-operated.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/monitoring/resources/ingress-infra.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/monitoring/secrets/alertmanager.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/networking/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/opa/kustomization.yaml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/opa/patches/gatekeeper-whitelist-namespace.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/opa/patches/infra-nodes.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/manifests/opa/resources/ingress-infra.yml.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/cert-manager.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/cluster-autoscaler.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/ebs-csi-driver.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/external-dns.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/load-balancer-controller.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/route53.tf.tpl create mode 100644 test/data/expensive/common/data/templates/distribution/terraform/velero.tf.tpl create mode 100644 test/data/expensive/create-complete/data/furyctl.yaml create mode 100644 test/data/expensive/create-skip-infra/data/furyctl.yaml create mode 100644 test/data/expensive/create-skip-kube/data/furyctl.yaml create mode 100644 test/data/integration/schema/santhosh/test-cluster-broken.json create mode 100644 test/data/integration/schema/santhosh/test-cluster-correct.json create mode 100644 test/data/integration/schema/santhosh/test-cluster-wrong.json create mode 100644 test/data/integration/v1.24.1/distro/furyctl-defaults.yaml create mode 100644 test/data/integration/v1.24.1/distro/kfd.yaml create mode 100644 test/data/integration/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json create mode 100644 test/data/integration/v1.24.1/furyctl.yaml create mode 100644 test/e2e/furyctl_test.go create mode 100644 test/expensive/furyctl_test.go delete mode 100644 test/helper.bash delete mode 100644 test/integration/template-engine/.gitignore delete mode 100644 test/integration/template-engine/data/complex-dry-run/source/kustomization.yaml.tpl delete mode 100644 test/integration/template-engine/data/complex/source/kustomization.yaml.tpl delete mode 100644 test/integration/template-engine/data/distribution-yaml-no-data-property/source/file.txt.tpl delete mode 100644 test/integration/template-engine/data/simple-dry-run/source/file.txt.tpl delete mode 100644 test/integration/template-engine/data/simple/source/file.txt.tpl delete mode 100644 test/integration/template-engine/tests.sh delete mode 100644 test/integration/validation-cmd/data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json delete mode 100644 test/integration/validation-cmd/data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json delete mode 100755 test/integration/validation-cmd/data/dependencies-correct/ansible delete mode 100755 test/integration/validation-cmd/data/dependencies-correct/kustomize delete mode 100644 test/integration/validation-cmd/data/dependencies-missing/furyctl.yaml delete mode 100644 test/integration/validation-cmd/data/dependencies-missing/kfd.yaml delete mode 100755 test/integration/validation-cmd/data/dependencies-wrong/ansible delete mode 100644 test/integration/validation-cmd/data/dependencies-wrong/furyctl.yaml delete mode 100644 test/integration/validation-cmd/data/dependencies-wrong/kfd.yaml delete mode 100644 test/integration/validation-cmd/tests.sh diff --git a/.drone.yml b/.drone.yml index 240a18436..9f69d1609 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ name: main steps: - name: prepare - image: quay.io/sighup/golang:1.19.0 + image: quay.io/sighup/golang:1.19.4 depends_on: - clone pull: always @@ -17,12 +17,11 @@ steps: - go mod download - name: license - image: quay.io/sighup/golang:1.19.0 + image: quay.io/sighup/golang:1.19.4 depends_on: - clone pull: always commands: - - go install github.com/google/addlicense@v1.0.0 - make license-check environment: CGO_ENABLED: 0 @@ -31,7 +30,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: lint - image: quay.io/sighup/golang:1.19.0 + image: quay.io/sighup/golang:1.19.4 depends_on: - prepare pull: always @@ -44,7 +43,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-unit - image: quay.io/sighup/golang:1.19.0 + image: quay.io/sighup/golang:1.19.4 depends_on: - prepare pull: always @@ -57,13 +56,11 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-integration - image: quay.io/sighup/golang:1.19.0 + image: quay.io/sighup/golang:1.19.4 depends_on: - prepare commands: - echo $${NETRC_FILE} > /root/.netrc - - git clone --depth 1 --branch v1.8.0 https://github.com/bats-core/bats-core.git - - ./bats-core/install.sh /usr/local - make test-integration environment: CGO_ENABLED: 0 @@ -73,13 +70,59 @@ steps: NETRC_FILE: from_secret: NETRC_FILE + - name: test-e2e + image: quay.io/sighup/golang:1.19.4 + depends_on: + - prepare + commands: + - apk add --update binutils curl groff openssh-client + # Install aws-cli + - curl -sL https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub + - curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/$${GLIBC_VER}/glibc-$${GLIBC_VER}.apk + - curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/$${GLIBC_VER}/glibc-bin-$${GLIBC_VER}.apk + - curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/$${GLIBC_VER}/glibc-i18n-$${GLIBC_VER}.apk + - apk add --force-overwrite --virtual .glibc glibc-$${GLIBC_VER}.apk glibc-bin-$${GLIBC_VER}.apk glibc-i18n-$${GLIBC_VER}.apk + - /usr/glibc-compat/bin/localedef -i en_US -f UTF-8 en_US.UTF-8 + - curl -sL https://awscli.amazonaws.com/awscli-exe-linux-x86_64-$${AWSCLI_VERSION}.zip -o awscliv2.zip + - unzip awscliv2.zip + - aws/install + - apk add gcompat || true + # Setup SSH + - mkdir /root/.ssh + - echo "$${GITHUB_SSH}" | tr -d '\r' | sed -e '$a\' > /root/.ssh/id_rsa + - echo $${NETRC_FILE} > /root/.netrc + - chmod 600 /root/.ssh/id_rsa + - touch /root/.ssh/known_hosts + - chmod 600 /root/.ssh/known_hosts + - ssh-keyscan -H github.com > /etc/ssh/ssh_known_hosts 2> /dev/null + # Run tests + - make test-e2e + environment: + CGO_ENABLED: 0 + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp + AWSCLI_VERSION: 2.8.12 + GLIBC_VER: 2.35-r0 + AWS_ACCESS_KEY_ID: + from_secret: AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY: + from_secret: AWS_SECRET_ACCESS_KEY + AWS_DEFAULT_REGION: + from_secret: AWS_REGION + FURYCTL_MIXPANEL_TOKEN: + from_secret: FURYCTL_MIXPANEL_TOKEN + NETRC_FILE: + from_secret: NETRC_FILE + GITHUB_SSH: + from_secret: GITHUB_SSH + - name: build image: ghcr.io/goreleaser/goreleaser:v1.11.4 depends_on: - prepare pull: always commands: - - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags - make build @@ -92,6 +135,8 @@ steps: GOCACHE: /drone/src/.go/cache GOMODCACHE: /drone/src/.go/modcache GOTMPDIR: /drone/src/.go/tmp + FURYCTL_MIXPANEL_TOKEN: + from_secret: FURYCTL_MIXPANEL_TOKEN GITHUB_TOKEN: from_secret: GITHUB_TOKEN @@ -103,7 +148,6 @@ steps: - test-unit - test-integration commands: - - go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 - git reset --hard - git fetch --tags - make release @@ -116,6 +160,8 @@ steps: GOCACHE: /drone/src/.go/cache GOMODCACHE: /drone/src/.go/modcache GOTMPDIR: /drone/src/.go/tmp + FURYCTL_MIXPANEL_TOKEN: + from_secret: FURYCTL_MIXPANEL_TOKEN GITHUB_TOKEN: from_secret: GITHUB_TOKEN diff --git a/.gitignore b/.gitignore index af8a69408..e20dc4bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,19 @@ -coverage.out +coverage.* +coverprofile.out vendor bin .idea -*-packr.go furyctl demo dist/ +.infrastructure +.kubernetes +.distribution +test/data/e2e/**/target +test/e2e/e2e.test + +.vscode +*.log +go.work +go.work.sum +.env diff --git a/.goreleaser.yml b/.goreleaser.yml index 75a9e987a..87be93269 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,7 +7,6 @@ project_name: furyctl before: hooks: - go mod tidy - - packr2 build builds: - env: - CGO_ENABLED=0 diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml index d82510b2a..528e5f572 100644 --- a/.rules/.golangci.yml +++ b/.rules/.golangci.yml @@ -23,48 +23,12 @@ linters: # Disable specific linter # https://golangci-lint.run/usage/linters/#disabled-by-default-linters--e--enable disable: - # todo - - cyclop - - errcheck - - errorlint - - forcetypeassert - - funlen - - gochecknoglobals - - gochecknoinits - - gocognit - - goconst - - gocritic - - godot - - godox - - goerr113 - - gomnd - - gosec - - gosimple - - govet - - ineffassign - - interfacebloat - - ireturn - - lll - - misspell - - nestif - - nilerr - - nlreturn - - nonamedreturns - - paralleltest - - revive - - staticcheck - - stylecheck - - tagliatelle - - tenv - - testpackage - - varnamelen - - whitespace - - wrapcheck - - wsl # unsupported - rowserrcheck - sqlclosecheck - wastedassign + - ireturn # not working with generics https://github.com/butuzov/ireturn/issues/37 + - varnamelen # not useful # deprecated - deadcode - exhaustivestruct @@ -101,6 +65,13 @@ linters-settings: - prefix(github.com/sighupio) # Custom section: groups all imports with the specified Prefix. skip-generated: true custom-order: true + cyclop: + max-complexity: 80 + funlen: + lines: 400 + statements: 182 + gocognit: + min-complexity: 210 godot: scope: all exclude: @@ -113,8 +84,12 @@ linters-settings: extra-rules: true goimports: local-prefixes: github.com/sighupio + gosec: + excludes: + - G204 + - G101 govet: - check-shadowing: true + check-shadowing: false grouper: const-require-single-const: true const-require-grouping: false @@ -122,6 +97,8 @@ linters-settings: import-require-grouping: false var-require-single-var: true var-require-grouping: false + nestif: + min-complexity: 60 nolintlint: require-explanation: true require-specific: true @@ -133,7 +110,7 @@ linters-settings: severity: warning disabled: false arguments: - - maxLitCount: "3" + - maxLitCount: "15" allowStrs: '""' allowInts: "0,1,2" allowFloats: "0.0,0.,1.0,1.,2.0,2." @@ -141,7 +118,7 @@ linters-settings: - name: argument-limit severity: warning disabled: false - arguments: [4] + arguments: [8] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic - name: atomic severity: warning @@ -158,7 +135,7 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports - name: blank-imports severity: warning - disabled: false + disabled: true # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr - name: bool-literal-in-expr severity: warning @@ -171,7 +148,7 @@ linters-settings: - name: cognitive-complexity severity: warning disabled: false - arguments: [7] + arguments: [210] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming - name: confusing-naming severity: warning @@ -179,7 +156,7 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results - name: confusing-results severity: warning - disabled: false + disabled: true # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr - name: constant-logical-expr severity: warning @@ -196,7 +173,7 @@ linters-settings: - name: cyclomatic severity: warning disabled: false - arguments: [3] + arguments: [80] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace - name: datarace severity: warning @@ -257,7 +234,7 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header - name: file-header severity: warning - disabled: false + disabled: true arguments: - This is the text that must appear at the top of source files. # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter @@ -268,12 +245,12 @@ linters-settings: - name: function-result-limit severity: warning disabled: false - arguments: [2] + arguments: [4] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length - name: function-length severity: warning disabled: false - arguments: [10, 0] + arguments: [200, 0] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return - name: get-return severity: warning @@ -308,13 +285,13 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit - name: line-length-limit severity: warning - disabled: false - arguments: [120] + disabled: true + arguments: [140] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs - name: max-public-structs severity: warning disabled: false - arguments: [3] + arguments: [10] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter - name: modifies-parameter severity: warning @@ -364,10 +341,10 @@ linters-settings: severity: warning disabled: false arguments: - - - 'core.WriteError[1].Message' - - '/^([^A-Z]|$)/' + - - "core.WriteError[1].Message" + - "/^([^A-Z]|$)/" - must not start with a capital letter - - - 'fmt.Errorf[0]' + - - "fmt.Errorf[0]" - '/(^|[^\.!?])$/' - must not end in punctuation - - panic @@ -448,7 +425,7 @@ linters-settings: varnamelen: check-receiver: true check-return: true - check-type-param: true + check-type-param: false ignore-type-assert-ok: true ignore-map-index-ok: true ignore-chan-recv-ok: true @@ -495,10 +472,13 @@ issues: - gosec - lll - maintidx + - revive - path: main\.go linters: - gochecknoglobals - + - path: cmd + linters: + - lll # Independently of option `exclude` we use default exclude patterns, # it can be disabled by this option. # To list all excluded by default patterns execute `golangci-lint run --help`. @@ -566,5 +546,5 @@ severity: # Default: [] rules: - linters: - - dupl + - dupl severity: info diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 85fe9879e..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,171 +0,0 @@ -# Contributing - -## Prerequisites - -Required go version >= 1.11 - -To install dependencies and run this package export env `GO111MODULE=on`. -It is required because the usage of `go mod` is opt-in at the time of writing. - -## Download dependencies - -Execute `go mod download` - -## Adding provisioners - -The self-service feature of `furyctl` has been designed to make it easily extensible. If you are in charge of adding -a new provisioner for bootstrapping or create a new cluster, read the following guide. - -The guide is almost identical between bootstrap and cluster. This guide focus on the `bootstrap` provisioners. -The main differences between bootstrap and cluster provisioners: - -- **`data/provisioner`** directory to hold terraform projects: - - **bootstrap**: `data/provisioners/bootstrap/{provisioner-name}` - - **cluster**: `data/provisioners/cluster/{provisioner-name}` -- **configuration parser** at `internal/configuration/config.go` has different methods: - - **bootstrap**: `bootstrapParser` - - **cluster**: `clusterParser`. -- **provisioner builder** at `internal/provisioners/provisioners.go` has different methods: - - **bootstrap**: `getBootstrapProvisioner`. - - **cluster**: `getClusterProvisioner`. -- The `configuration struct` goes - - bootstrap at: `internal/bootstrap/configuration/{provisioner-name}.go`. - - cluster: `internal/cluster/configuration/{provisioner-name}.go`. -- The `templated configuration` goes: - - `internal/configuration/templates.go`. Create the desired configuration file to let the `furyctl` output it as `yml` -- The `provisioner implementation` goes - - **bootstrap** at `internal/bootstrap/provisioners/{provisioner-name}/provisioner.go` - - **cluster** at `internal/cluster/provisioners/{provisioner-name}/provisioner.go` - -### Terraform project - -First of all, `furyctl` provisioners are based on terraform code. So you have to have an already -working terraform project. -Place the terraform project in the following `data/provisioners/bootstrap/{provisioner-name}` path. - -**Let's use `dummy` as the `provisioner-name`.** - -### Configuration parser - -Then, create the golang structures that will be used to hold the input variables. -Create the `internal/bootstrap/configuration/dummy.go` file with a `struct` definition: - -```golang -package configuration - -// Dummy reprensents the input variables of a dummy provisioner. -// THIS IS AN EXAMPLE. Create a struct definition that fits the input variables of your terraform project. -type Dummy struct { - NetworkCIDR string `yaml:"networkCIDR"` - PublicSubnetsCIDRs []string `yaml:"publicSubnetsCIDRs"` - PrivateSubnetsCIDRs []string `yaml:"privateSubnetsCIDRs"` -} -``` - -Then, add the `Dummy` configuration to the configuration parser in the `internal/configuration/config.go` file -*(new swith option)*: - -```diff ---- a/internal/configuration/config.go -+++ b/internal/configuration/config.go -@@ -122,6 +122,15 @@ func bootstrapParser(config *Configuration) (err error) { - } - config.Spec = awsSpec - return nil -+ case provisioner == "dummy": -+ dummySpec := bootstrapcfg.Dummy{} -+ err = yaml.Unmarshal(specBytes, &dummySpec) -+ if err != nil { -+ logrus.Errorf("error parsing configuration file: %v", err) -+ return err -+ } -+ config.Spec = dummySpec -+ return nil - default: - logrus.Error("Error parsing the configuration file. Provisioner not found") - return errors.New("Bootstrap provisioner not found") -``` - -The configuration structures that we created is part of the entire configuration file. -It is the structure that gives value to the `spec` base configuration attribute. - -### Provisioner interface implementation - -Then, create a new directory/package inside `internal/bootstrap/provisioners/` named `dummy`. -Create a golang file: `internal/bootstrap/provisioners/dummy/provisioner.go` then implement the provisioner interface -available at `internal/provisioners/provisioners.go`: - -```golang -// Provisioner represents a kubernetes terraform provisioner -type Provisioner interface { - InitMessage() string // Prints this message after init phase finishes - UpdateMessage() string // Prints this message after update phase finishes - DestroyMessage() string // Prints this message after destroy phase finishes - - SetTerraformExecutor(tf *tfexec.Terraform) // Configures a terraform executor once initialized - TerraformExecutor() (tf *tfexec.Terraform) // Returns the provisioner terraform executor - TerraformFiles() []string // Returns the list of terraform files that makes the provisioner. - - Enterprise() bool // Indicates if it requires a valid token. Contact sales@sighup.io - - Plan() error // Execute the terraform plan command - Update() error // Execute the terraform apply command - Destroy() error // Execute the terraform destroy command - - Box() *packr.Box // Returns a `box` with the binary data of the terraform files. -} -``` - -*Take a look at the AWS implementation to understand how it works*: `internal/bootstrap/provisioners/aws/provisioner.go` - -Once implemented, include it in the available provisioner switch at `internal/provisioners/provisioners.go` - -```diff -index 8c5a6d8..a3d6ecf 100644 ---- a/internal/provisioners/provisioners.go -+++ b/internal/provisioners/provisioners.go -@@ -11,6 +11,7 @@ import ( - "github.com/gobuffalo/packr/v2" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sighupio/furyctl/internal/bootstrap/provisioners/aws" -+ "github.com/sighupio/furyctl/internal/bootstrap/provisioners/dummy" - "github.com/sighupio/furyctl/internal/cluster/provisioners/eks" - "github.com/sighupio/furyctl/internal/configuration" - log "github.com/sirupsen/logrus" -@@ -61,6 +62,8 @@ func getBootstrapProvisioner(config configuration.Configuration) (Provisioner, e - switch { - case config.Provisioner == "aws": - return aws.New(&config), nil -+ case config.Provisioner == "dummy": -+ return dummy.New(&config), nil - default: - logrus.Error("Provisioner not found") - return nil, errors.New("Provisioner not found") -``` - -Then, build the `furyctl` binary: `make build`, then create a configuration file: `dummy-bootstrap.yml` - -```yaml -kind: Bootstrap # Important! -metadata: - name: my-dummy-demo -provisioner: dummy # Important! This is the provider we have created. -spec: # This is managed by the configuration struct - networkCIDR: "10.0.0.0/16" - publicSubnetsCIDRs: - - "10.0.1.0/24" - - "10.0.2.0/24" - - "10.0.3.0/24" - privateSubnetsCIDRs: - - "10.0.101.0/24" - - "10.0.102.0/24" - - "10.0.103.0/24" -``` - -All is set to start using the new provisioner: - -```bash -$ furyctl bootstrap init -c dummy-bootstrap.yml -``` - -Enjoy! diff --git a/Makefile b/Makefile index 1300de775..a7c6e8a87 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,15 @@ _PROJECT_DIRECTORY = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) -_GOLANG_IMAGE = golang:1.19.1 +_GOLANG_IMAGE = golang:1.19.4 _PROJECTNAME = furyctl _GOARCH = "amd64" +_BIN_OPEN = "open" NETRC_FILE ?= ~/.netrc +ifeq ("$(shell uname -s)", "Linux") + _BIN_OPEN = "xdg-open" +endif + ifeq ("$(shell uname -m)", "arm64") _GOARCH = "arm64" endif @@ -22,13 +27,33 @@ define run-docker $(1) $(2) endef -.PHONY: env +#1: message +define yes-or-no + @while true; do \ + read -r -p ${1}" [y/n]: " yn ; \ + case "$${yn}" in \ + [Yy]) break ;; \ + [Nn]) echo "Aborted, exiting..."; exit 1 ;; \ + esac \ + done +endef + +.PHONY: env tools env: @echo 'export CGO_ENABLED=0' @echo 'export GOARCH=${_GOARCH}' @grep -v '^#' .env | sed 's/^/export /' +tools: + @go install github.com/daixiang0/gci@latest + @go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + @go install github.com/google/addlicense@latest + @go install github.com/nikolaydubina/go-cover-treemap@latest + @go install github.com/onsi/ginkgo/v2/ginkgo@latest + @go install golang.org/x/tools/cmd/goimports@latest + @go install mvdan.cc/gofumpt@latest + .PHONY: mod-download mod-tidy mod-verify mod-download: @@ -104,17 +129,33 @@ gci: lint: lint-go lint-go: - @golangci-lint -v run --color=always --config=${_PROJECT_DIRECTORY}/.rules/.golangci.yml ./... + @GOFLAGS=-mod=mod golangci-lint -v run --color=always --config=${_PROJECT_DIRECTORY}/.rules/.golangci.yml ./... -.PHONY: test-unit test-integration +.PHONY: test-unit test-integration test-e2e test-all show-coverage test-unit: - @go test -v -covermode=atomic -coverprofile=coverage.out -tags=unit ./... + @GOFLAGS=-mod=mod go test -v -tags=unit ./... test-integration: - @go test -v -covermode=atomic -coverprofile=coverage.out -tags=integration -timeout 120s ./... - @bats -t ./test/integration/template-engine/tests.sh - @bats -t ./test/integration/validation-cmd/tests.sh + @GOFLAGS=-mod=mod go test -v -tags=integration -timeout 120s ./... + +test-e2e: + @GOFLAGS=-mod=mod ginkgo run -vv --trace -tags=e2e -timeout 300s -p test/e2e + +test-expensive: + $(call yes-or-no, "WARNING: This test will create a cluster on AWS. Are you sure you want to continue?") + @GOFLAGS=-mod=mod ginkgo run -vv --trace -tags=expensive -timeout 36000s -p test/expensive + +test-most: + @GOFLAGS=-mod=mod ginkgo run -vv --trace -covermode=count -coverprofile=coverage.out -tags=unit,integration,e2e,expensive --skip-package=expensive -timeout 300s -p ./... + +test-all: + $(call yes-or-no, "WARNING: This test will create a cluster on AWS. Are you sure you want to continue?") + @GOFLAGS=-mod=mod ginkgo run -vv --trace -covermode=count -coverprofile=coverage.out -tags=unit,integration,e2e,expensive -timeout 300s -p ./... + +show-coverage: + @go tool cover -html=coverage.out -o coverage.html + @go-cover-treemap -coverprofile coverage.out > coverage.svg && ${_BIN_OPEN} coverage.svg .PHONY: clean build release diff --git a/README.md b/README.md index 4dd660d0a..7e1211a65 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,22 @@ The available `cluster` provisioners are: Before contributing, please read first the [Contributing Guidelines](docs/CONTRIBUTING.md). +## Test classes + +There are four kind of tests: unit, integration, e2e, and expensive. + +Each of them covers specific use cases depending on the speed, cost, and dependencies at play in a given scenario. +Anything that uses i/o should be marked as integration, with the only expection of local files and folders: any test +that uses the local filesystem and nothing more can be marked as 'unit'. This is made for convenience and it's open to +change in the future should we decide to refactor the code to better isolate that kind of i/o from the logic of the tool. + +That said, here's a little summary of the used tags: + +- unit: tests that exercise a single component or function in isolation. Tests using local files and dirs are permitted here. +- integration: tests that require external services, such as github. Test using only local files and dirs should not be marked as integration. +- e2e: tests that exercise furyctl binary, invoking it as a cli tool and checking its output +- expensive: e2e tests that incur in some monetary cost, like running an EKS instance on AWS + ### Reporting Issues In case you experience any problems, please [open a new issue](https://github.com/sighupio/furyctl/issues/new/choose). diff --git a/cmd/completion.go b/cmd/completion.go index 13dd57dae..38903092b 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -5,12 +5,26 @@ package cmd import ( + "errors" + "fmt" "os" "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/analytics" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" +) + +var ( + ErrBashCompletion = errors.New("error generating bash completion") + ErrZshCompletion = errors.New("error generating zsh completion") + ErrFishCompletion = errors.New("error generating fish completion") + ErrPowershellCompletion = errors.New("error generating powershell completion") ) -func NewCompletionCmd() *cobra.Command { +func NewCompletionCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + return &cobra.Command{ Use: "completion [bash|zsh|fish|powershell]", Short: "Generate completion script", @@ -55,18 +69,47 @@ func NewCompletionCmd() *cobra.Command { `, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, - Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, + + RunE: func(cmd *cobra.Command, args []string) error { switch args[0] { case "bash": - cmd.Root().GenBashCompletion(os.Stdout) + if err := cmd.Root().GenBashCompletion(os.Stdout); err != nil { + cmdEvent.AddErrorMessage(ErrBashCompletion) + tracker.Track(cmdEvent) + + return fmt.Errorf("error generating bash completion: %w", err) + } case "zsh": - cmd.Root().GenZshCompletion(os.Stdout) + if err := cmd.Root().GenZshCompletion(os.Stdout); err != nil { + cmdEvent.AddErrorMessage(ErrZshCompletion) + tracker.Track(cmdEvent) + + return fmt.Errorf("error generating zsh completion: %w", err) + } case "fish": - cmd.Root().GenFishCompletion(os.Stdout, true) + if err := cmd.Root().GenFishCompletion(os.Stdout, true); err != nil { + cmdEvent.AddErrorMessage(ErrFishCompletion) + tracker.Track(cmdEvent) + + return fmt.Errorf("error generating fish completion: %w", err) + } case "powershell": - cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + if err := cmd.Root().GenPowerShellCompletion(os.Stdout); err != nil { + cmdEvent.AddErrorMessage(ErrPowershellCompletion) + tracker.Track(cmdEvent) + + return fmt.Errorf("error generating powershell completion: %w", err) + } } + + cmdEvent.AddSuccessMessage("completion generated for " + args[0]) + tracker.Track(cmdEvent) + + return nil }, } } diff --git a/cmd/create.go b/cmd/create.go new file mode 100644 index 000000000..0d0a118a9 --- /dev/null +++ b/cmd/create.go @@ -0,0 +1,24 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/cmd/create" + "github.com/sighupio/furyctl/internal/analytics" +) + +func NewCreateCommand(tracker *analytics.Tracker) *cobra.Command { + createCmd := &cobra.Command{ + Use: "create", + Short: "Create a cluster or a config file", + } + + createCmd.AddCommand(create.NewClusterCmd(tracker)) + createCmd.AddCommand(create.NewConfigCmd(tracker)) + + return createCmd +} diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go new file mode 100644 index 000000000..ebcf3846d --- /dev/null +++ b/cmd/create/cluster.go @@ -0,0 +1,370 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package create + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/analytics" + _ "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/dependencies" + "github.com/sighupio/furyctl/internal/distribution" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" + netx "github.com/sighupio/furyctl/internal/x/net" +) + +var ( + ErrParsingFlag = errors.New("error while parsing flag") + ErrDownloadDependenciesFailed = errors.New("dependencies download failed") + ErrKubeconfigReq = errors.New("when running distribution phase alone, either the KUBECONFIG environment variable or the --kubeconfig flag should be set") +) + +type ClusterCmdFlags struct { + Debug bool + FuryctlPath string + DistroLocation string + Phase string + SkipPhase string + BinPath string + VpnAutoConnect bool + DryRun bool + SkipDepsDownload bool + SkipDepsValidation bool + Kubeconfig string +} + +func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + + cmd := &cobra.Command{ + Use: "cluster", + Short: "Creates a battle-tested Kubernetes Fury cluster", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + // Get flags. + flags, err := getCmdFlags(cmd, tracker, cmdEvent) + if err != nil { + return err + } + + // Init paths. + homeDir, err := os.UserHomeDir() + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while getting current working directory: %w", err) + } + + // Check if kubeconfig is needed. + if flags.Phase == cluster.OperationPhaseDistribution || flags.SkipPhase == cluster.OperationPhaseKubernetes { + if flags.Kubeconfig == "" { + kubeconfigFromEnv := os.Getenv("KUBECONFIG") + + if kubeconfigFromEnv == "" { + return ErrKubeconfigReq + } + + logrus.Warnf("Missing --kubeconfig flag, falling back to KUBECONFIG from environment: %s", kubeconfigFromEnv) + } + } + + if flags.BinPath == "" { + flags.BinPath = filepath.Join(homeDir, ".furyctl", "bin") + } + + if flags.DryRun { + logrus.Info("Dry run mode enabled, no changes will be applied") + } + + // Init first half of collaborators. + client := netx.NewGoGetterClient() + executor := execx.NewStdExecutor() + distrodl := distribution.NewDownloader(client) + + // Init packages. + execx.Debug = flags.Debug || flags.DryRun + + // Download the distribution. + logrus.Info("Downloading distribution...") + res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) + + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: res.DistroManifest.Kubernetes.Eks.Version, + KFDVersion: res.DistroManifest.Version, + Phase: flags.Phase, + }) + + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while downloading distribution: %w", err) + } + + basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) + + // Init second half of collaborators. + depsdl := dependencies.NewDownloader(client, basePath, flags.BinPath) + depsvl := dependencies.NewValidator(executor, flags.BinPath) + + // Validate the furyctl.yaml file. + logrus.Info("Validating furyctl.yaml file...") + if err := config.Validate(flags.FuryctlPath, res.RepoPath); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating furyctl.yaml file: %w", err) + } + + // Download the dependencies. + if !flags.SkipDepsDownload { + logrus.Info("Downloading dependencies...") + if errs, _ := depsdl.DownloadAll(res.DistroManifest); len(errs) > 0 { + cmdEvent.AddErrorMessage(ErrDownloadDependenciesFailed) + tracker.Track(cmdEvent) + + return fmt.Errorf("%w: %v", ErrDownloadDependenciesFailed, errs) + } + } + + // Validate the dependencies, unless explicitly told to skip it. + if !flags.SkipDepsValidation { + logrus.Info("Validating dependencies...") + if err := depsvl.Validate(res); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating dependencies: %w", err) + } + } + + // Auto connect to the VPN if doing complete cluster creation or skipping distribution phase. + if flags.Phase == cluster.OperationPhaseAll || flags.SkipPhase == cluster.OperationPhaseDistribution { + flags.VpnAutoConnect = true + } + + // Define cluster creation paths. + paths := cluster.CreatorPaths{ + ConfigPath: flags.FuryctlPath, + WorkDir: basePath, + DistroPath: res.RepoPath, + BinPath: flags.BinPath, + Kubeconfig: flags.Kubeconfig, + } + + // Create the cluster. + clusterCreator, err := cluster.NewCreator( + res.MinimalConf, + res.DistroManifest, + paths, + flags.Phase, + flags.VpnAutoConnect, + flags.DryRun, + ) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while initializing cluster creation: %w", err) + } + + logrus.Info("Creating cluster...") + if err := clusterCreator.Create(flags.SkipPhase); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while creating cluster: %w", err) + } + + if !flags.DryRun && flags.Phase == cluster.OperationPhaseAll { + logrus.Info("Cluster created successfully!") + } + + if flags.Phase != cluster.OperationPhaseAll { + logrus.Infof("Phase %s executed successfully!", flags.Phase) + } + + cmdEvent.AddSuccessMessage("cluster creation succeeded") + tracker.Track(cmdEvent) + + return nil + }, + } + + setupClusterCmdFlags(cmd) + + return cmd +} + +func getCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (ClusterCmdFlags, error) { + debug, err := cmdutil.BoolFlag(cmd, "debug", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "debug") + } + + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "config") + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "distro-location") + } + + phase, err := cmdutil.StringFlag(cmd, "phase", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "phase") + } + + err = cluster.CheckPhase(phase) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s: %s", ErrParsingFlag, "phase", err.Error()) + } + + skipPhase, err := cmdutil.StringFlag(cmd, "skip-phase", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-phase") + } + + if phase != cluster.OperationPhaseAll && skipPhase != "" { + return ClusterCmdFlags{}, fmt.Errorf( + "%w: %s: cannot use together with phase flag", + ErrParsingFlag, + "skip-phase", + ) + } + + err = cluster.CheckPhase(skipPhase) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s: %s", ErrParsingFlag, "skip-phase", err.Error()) + } + + binPath := cmdutil.StringFlagOptional(cmd, "bin-path") + + vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-auto-connect") + } + + dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "dry-run") + } + + skipDepsDownload, err := cmdutil.BoolFlag(cmd, "skip-deps-download", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-deps-download") + } + + skipDepsValidation, err := cmdutil.BoolFlag(cmd, "skip-deps-validation", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-deps-validation") + } + + kubeconfig, err := cmdutil.StringFlag(cmd, "kubeconfig", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "kubeconfig") + } + + return ClusterCmdFlags{ + Debug: debug, + FuryctlPath: furyctlPath, + DistroLocation: distroLocation, + Phase: phase, + SkipPhase: skipPhase, + BinPath: binPath, + VpnAutoConnect: vpnAutoConnect, + DryRun: dryRun, + SkipDepsDownload: skipDepsDownload, + SkipDepsValidation: skipDepsValidation, + Kubeconfig: kubeconfig, + }, nil +} + +func setupClusterCmdFlags(cmd *cobra.Command) { + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + cmd.Flags().StringP( + "phase", + "p", + "", + "Limit the execution to a specific phase. options are: infrastructure, kubernetes, distribution", + ) + + cmd.Flags().String( + "skip-phase", + "", + "Avoid executing a unwanted phase. options are: infrastructure, kubernetes, distribution. More specifically:\n"+ + "- skipping infrastructure will execute kubernetes and distribution\n"+ + "- skipping kubernetes will only execute distribution\n"+ + "- skipping distribution will execute infrastructure and kubernetes\n", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Location where to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME). "+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + cmd.Flags().StringP( + "bin-path", + "b", + "", + "Path to the bin folder where all dependencies are installed", + ) + + cmd.Flags().Bool( + "skip-deps-download", + false, + "Skip downloading the distribution modules, installers and binaries", + ) + + cmd.Flags().Bool( + "skip-deps-validation", + false, + "Skip validating dependencies", + ) + + cmd.Flags().Bool( + "dry-run", + false, + "Allows to inspect what resources will be created before applying them", + ) + + cmd.Flags().Bool( + "vpn-auto-connect", + false, + "When set will automatically connect to the created VPN in the infrastructure phase", + ) + + cmd.Flags().String( + "kubeconfig", + "", + "Path to the kubeconfig file, mandatory if you want to run the distribution phase and the KUBECONFIG environment variable is not set", + ) +} diff --git a/cmd/create/config.go b/cmd/create/config.go new file mode 100644 index 000000000..3c182460f --- /dev/null +++ b/cmd/create/config.go @@ -0,0 +1,176 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package create + +import ( + "errors" + "fmt" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + distroConfig "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" + netx "github.com/sighupio/furyctl/internal/x/net" +) + +var ErrMandatoryFlag = errors.New("flag must be specified") + +func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + + cmd := &cobra.Command{ + Use: "config", + Short: "scaffolds a new furyctl config file", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + // Get flags. + debug, err := cmdutil.BoolFlag(cmd, "debug", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: %s", ErrParsingFlag, "debug") + } + + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: config", ErrParsingFlag) + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: %s", ErrParsingFlag, "distro-location") + } + + version, err := cmdutil.StringFlag(cmd, "version", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: version", ErrParsingFlag) + } + if version == "" { + return fmt.Errorf("%w: version", ErrMandatoryFlag) + } + + kind, err := cmdutil.StringFlag(cmd, "kind", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: kind", ErrParsingFlag) + } + + apiVersion, err := cmdutil.StringFlag(cmd, "api-version", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: api-version", ErrParsingFlag) + } + + name, err := cmdutil.StringFlag(cmd, "name", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: name", ErrParsingFlag) + } + + minimalConf := distroConfig.Furyctl{ + APIVersion: apiVersion, + Kind: kind, + Metadata: distroConfig.FuryctlMeta{ + Name: name, + }, + Spec: distroConfig.FuryctlSpec{ + DistributionVersion: semver.EnsurePrefix(version), + }, + } + + // Init collaborators. + distrodl := distribution.NewDownloader(netx.NewGoGetterClient()) + + // Init packages. + execx.Debug = debug + + // Download the distribution. + logrus.Info("Downloading distribution...") + res, err := distrodl.DoDownload(distroLocation, minimalConf) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("failed to download distribution: %w", err) + } + + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + KFDVersion: res.DistroManifest.Version, + }) + + data := map[string]string{ + "Kind": kind, + "Name": name, + "DistributionVersion": semver.EnsurePrefix(version), + } + + out, err := config.Create(res, furyctlPath, cmdEvent, tracker, data) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("failed to create config file: %w", err) + } + + logrus.Infof("Config file created successfully at: %s", out.Name()) + + cmdEvent.AddSuccessMessage(fmt.Sprintf("Config file created successfully at: %s", out.Name())) + tracker.Track(cmdEvent) + + return nil + }, + } + + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Base URL used to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + cmd.Flags().StringP( + "version", + "v", + "", + "fury version to use (eg: v1.24.1)", + ) + + cmd.Flags().StringP( + "kind", + "k", + "EKSCluster", + "type of cluster to create (eg: EKSCluster)", + ) + + cmd.Flags().StringP( + "api-version", + "a", + "kfd.sighup.io/v1alpha2", + "version of the api to use for the selected kind (eg: kfd.sighup.io/v1alpha2)", + ) + + cmd.Flags().StringP( + "name", + "n", + "example", + "name of cluster to create", + ) + + return cmd +} diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 000000000..6af68c6af --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,23 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/spf13/cobra" + + del "github.com/sighupio/furyctl/cmd/delete" + "github.com/sighupio/furyctl/internal/analytics" +) + +func NewDeleteCommand(tracker *analytics.Tracker) *cobra.Command { + deleteCmd := &cobra.Command{ + Use: "delete", + Short: "Delete a cluster", + } + + deleteCmd.AddCommand(del.NewClusterCmd(tracker)) + + return deleteCmd +} diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go new file mode 100644 index 000000000..e5526ca25 --- /dev/null +++ b/cmd/delete/cluster.go @@ -0,0 +1,257 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package del + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/dependencies" + "github.com/sighupio/furyctl/internal/distribution" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" + netx "github.com/sighupio/furyctl/internal/x/net" +) + +var ( + ErrParsingFlag = errors.New("error while parsing flag") + ErrKubeconfigReq = errors.New("when running distribution phase, either the KUBECONFIG environment variable or the --kubeconfig flag should be set") +) + +func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + + cmd := &cobra.Command{ + Use: "cluster", + Short: "Deletes a cluster", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + debug, err := cmdutil.BoolFlag(cmd, "debug", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: debug", ErrParsingFlag) + } + + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: config", ErrParsingFlag) + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: distro-location", ErrParsingFlag) + } + + phase, err := cmdutil.StringFlag(cmd, "phase", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: phase", ErrParsingFlag) + } + + err = cluster.CheckPhase(phase) + if err != nil { + return fmt.Errorf("%w: %s: %s", ErrParsingFlag, "phase", err.Error()) + } + + binPath := cobrax.Flag[string](cmd, "bin-path").(string) //nolint:errcheck,forcetypeassert // optional flag + dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: dry-run", ErrParsingFlag) + } + + force, err := cmdutil.BoolFlag(cmd, "force", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: force", ErrParsingFlag) + } + + kubeconfig, err := cmdutil.StringFlag(cmd, "kubeconfig", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: %s", ErrParsingFlag, "kubeconfig") + } + + // Init paths. + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("error while getting user home directory: %w", err) + } + + // Check if kubeconfig is needed. + if phase == cluster.OperationPhaseDistribution || phase == cluster.OperationPhaseAll { + if kubeconfig == "" { + kubeconfigFromEnv := os.Getenv("KUBECONFIG") + + if kubeconfigFromEnv == "" { + return ErrKubeconfigReq + } + + logrus.Warnf("Missing --kubeconfig flag, falling back to KUBECONFIG from environment: %s", kubeconfigFromEnv) + } + } + + if binPath == "" { + binPath = filepath.Join(homeDir, ".furyctl", "bin") + } + + // Init first half of collaborators. + client := netx.NewGoGetterClient() + executor := execx.NewStdExecutor() + distrodl := distribution.NewDownloader(client) + + execx.Debug = debug || dryRun + + // Download the distribution. + logrus.Info("Downloading distribution...") + res, err := distrodl.Download(distroLocation, furyctlPath) + if err != nil { + err = fmt.Errorf("error while downloading distribution: %w", err) + + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return err + } + + basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) + + // Init second half of collaborators. + depsvl := dependencies.NewValidator(executor, binPath) + + // Validate the dependencies. + logrus.Info("Validating dependencies...") + if err := depsvl.Validate(res); err != nil { + return fmt.Errorf("error while validating dependencies: %w", err) + } + + clusterDeleter, err := cluster.NewDeleter( + res.MinimalConf, + res.DistroManifest, + phase, + basePath, + binPath, + kubeconfig, + ) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while initializing cluster deleter: %w", err) + } + + if !force { + _, err = fmt.Println("WARNING: You are about to delete a cluster. This action is irreversible.") + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while printing to stdout: %w", err) + } + + _, err = fmt.Println("Are you sure you want to continue? Only 'yes' will be accepted to confirm.") + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while printing to stdout: %w", err) + } + + if !askForConfirmation() { + return nil + } + } + + err = clusterDeleter.Delete(dryRun) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while deleting cluster: %w", err) + } + + if !dryRun && phase == cluster.OperationPhaseAll { + logrus.Info("Cluster deleted successfully!") + } + + cmdEvent.AddSuccessMessage("Cluster deleted successfully!") + tracker.Track(cmdEvent) + + return nil + }, + } + + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the furyctl.yaml file", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Base URL used to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + cmd.Flags().StringP( + "bin-path", + "b", + "", + "Path to the bin folder where all dependencies are installed", + ) + + cmd.Flags().StringP( + "phase", + "p", + "", + "Phase to execute", + ) + + cmd.Flags().Bool( + "dry-run", + false, + "Allows to inspect what resources will be deleted", + ) + + cmd.Flags().Bool( + "force", + false, + "Force deletion of the cluster", + ) + + cmd.Flags().String( + "kubeconfig", + "", + "Path to the kubeconfig file, mandatory if you want to run the distribution phase alone or "+ + "if you want to delete a cluster and the KUBECONFIG environment variable is not set", + ) + + return cmd +} + +func askForConfirmation() bool { + reader := bufio.NewReader(os.Stdin) + + response, err := reader.ReadString('\n') + if err != nil { + return false + } + + response = strings.TrimSuffix(response, "\n") + + return strings.Compare(response, "yes") == 0 +} diff --git a/cmd/download.go b/cmd/download.go index 4de163551..b7857a1c8 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -8,15 +8,16 @@ import ( "github.com/spf13/cobra" "github.com/sighupio/furyctl/cmd/download" + "github.com/sighupio/furyctl/internal/analytics" ) -func NewDownloadCmd(furyctlBinVersion string) *cobra.Command { +func NewDownloadCmd(tracker *analytics.Tracker) *cobra.Command { dumpCmd := &cobra.Command{ Use: "download", Short: "Dowload fury files", } - dumpCmd.AddCommand(download.NewDependenciesCmd(furyctlBinVersion)) + dumpCmd.AddCommand(download.NewDependenciesCmd(tracker)) return dumpCmd } diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 352041f2a..606db3b81 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -5,65 +5,118 @@ package download import ( + "errors" "fmt" "os" + "path/filepath" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/cobrax" - "github.com/sighupio/furyctl/internal/netx" + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/dependencies" + "github.com/sighupio/furyctl/internal/distribution" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + netx "github.com/sighupio/furyctl/internal/x/net" ) -var ErrDownloadFailed = fmt.Errorf("dependencies download failed") +var ( + ErrParsingFlag = errors.New("error while parsing flag") + ErrDownloadFailed = errors.New("dependencies download failed") +) + +func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event -func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", Short: "Download dependencies", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, RunE: func(cmd *cobra.Command, _ []string) error { - debug := cobrax.Flag[bool](cmd, "debug").(bool) - furyctlPath := cobrax.Flag[string](cmd, "config").(string) - distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: config", ErrParsingFlag) + } - basePath, err := os.Getwd() + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) if err != nil { - return err + return fmt.Errorf("%w: distro-location", ErrParsingFlag) } - dd := app.NewDownloadDependencies(netx.NewGoGetterClient(), basePath) + binPath := cmdutil.StringFlagOptional(cmd, "bin-path") + + homeDir, err := os.UserHomeDir() + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while getting user home directory: %w", err) + } + + if binPath == "" { + binPath = filepath.Join(homeDir, ".furyctl", "bin") + } + + logrus.Info("Downloading dependencies...") + + client := netx.NewGoGetterClient() - res, err := dd.Execute(app.DownloadDependenciesRequest{ - FuryctlBinVersion: furyctlBinVersion, - DistroLocation: distroLocation, - FuryctlConfPath: furyctlPath, - Debug: debug, + distrodl := distribution.NewDownloader(client) + + dres, err := distrodl.Download(distroLocation, furyctlPath) + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + KFDVersion: dres.DistroManifest.Version, }) + if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("failed to download distribution: %w", err) } - for _, ut := range res.UnsupTools { - logrus.Warn(fmt.Sprintf("'%s' download is not supported", ut)) + basePath := filepath.Join(homeDir, ".furyctl", dres.MinimalConf.Metadata.Name) + + depsdl := dependencies.NewDownloader(client, basePath, binPath) + + errs, uts := depsdl.DownloadAll(dres.DistroManifest) + + for _, ut := range uts { + logrus.Warn(fmt.Sprintf("'%s' download is not supported, please install it manually", ut)) } - if res.HasErrors() { - logrus.Debugf("Repository path: %s", res.RepoPath) + if len(errs) > 0 { + logrus.Debugf("Repository path: %s", dres.RepoPath) - for _, err := range res.DepsErrors { + for _, err := range errs { logrus.Error(err) } + cmdEvent.AddErrorMessage(ErrDownloadFailed) + tracker.Track(cmdEvent) + return ErrDownloadFailed } logrus.Info("Dependencies download succeeded") + cmdEvent.AddSuccessMessage("Dependencies download succeeded") + tracker.Track(cmdEvent) + return nil }, } + cmd.Flags().StringP( + "bin-path", + "b", + "", + "Path to the bin folder where all dependencies are installed", + ) + cmd.Flags().StringP( "config", "c", @@ -77,7 +130,7 @@ func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: https://git@github.com/sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/dump.go b/cmd/dump.go index d5ae1bda1..f77ca2916 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -8,15 +8,16 @@ import ( "github.com/spf13/cobra" "github.com/sighupio/furyctl/cmd/dump" + "github.com/sighupio/furyctl/internal/analytics" ) -func NewDumpCmd() *cobra.Command { +func NewDumpCmd(tracker *analytics.Tracker) *cobra.Command { dumpCmd := &cobra.Command{ Use: "dump", Short: "Dump templates and other useful fury objects", } - dumpCmd.AddCommand(dump.NewTemplateCmd()) + dumpCmd.AddCommand(dump.NewTemplateCmd(tracker)) return dumpCmd } diff --git a/cmd/dump/template.go b/cmd/dump/template.go index e7528b208..973acc5e0 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -12,17 +12,23 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" - "github.com/sighupio/furyctl/internal/yaml" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) +var ErrSourceDirDoesNotExist = fmt.Errorf("source directory does not exist") + type templateConfig struct { DryRun bool NoOverwrite bool } -func NewTemplateCmd() *cobra.Command { +func NewTemplateCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + cfg := templateConfig{} templateCmd := &cobra.Command{ Use: "template", @@ -31,26 +37,37 @@ func NewTemplateCmd() *cobra.Command { The generated folder will be created starting from a provided template and the parameters set in a configuration file that is merged with default values.`, SilenceUsage: true, SilenceErrors: true, + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, RunE: func(_ *cobra.Command, _ []string) error { - // TODO(rm-2470): To be reworked in redmine task - Define template command flags. - source := "source" + source := "templates/distribution" target := "target" suffix := ".tpl" - distributionFilePath := "distribution.yaml" + distributionFilePath := "furyctl-defaults.yaml" furyctlFilePath := "furyctl.yaml" - distributionFile, err := yaml.FromFileV2[map[any]any](distributionFilePath) + distributionFile, err := yamlx.FromFileV2[map[any]any](distributionFilePath) if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return fmt.Errorf("%s - %w", distributionFilePath, err) } - furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) + furyctlFile, err := yamlx.FromFileV2[map[any]any](furyctlFilePath) if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return fmt.Errorf("%s - %w", furyctlFilePath, err) } if _, err := os.Stat(source); os.IsNotExist(err) { - return fmt.Errorf("source directory does not exist") + cmdEvent.AddErrorMessage(ErrSourceDirDoesNotExist) + tracker.Track(cmdEvent) + + return ErrSourceDirDoesNotExist } merger := merge.NewMerger( @@ -58,19 +75,49 @@ The generated folder will be created starting from a provided template and the p merge.NewDefaultModel(furyctlFile, ".spec.distribution"), ) - mergedDistribution, err := merger.Merge() + _, err = merger.Merge() + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error merging files: %w", err) + } + + reverseMerger := merge.NewMerger( + *merger.GetCustom(), + *merger.GetBase(), + ) + + _, err = reverseMerger.Merge() if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error merging files: %w", err) } - outYaml, err := yaml.MarshalV2(mergedDistribution) + tmplCfg, err := template.NewConfig(reverseMerger, reverseMerger, []string{}) if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error creating template config: %w", err) + } + + outYaml, err := yamlx.MarshalV2(tmplCfg) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error marshaling template config: %w", err) } outDirPath, err := os.MkdirTemp("", "furyctl-dist-") if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error creating temporary directory: %w", err) } confPath := filepath.Join(outDirPath, "config.yaml") @@ -78,12 +125,18 @@ The generated folder will be created starting from a provided template and the p logrus.Debugf("config path = %s", confPath) if err = os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error writing config file: %w", err) } if !cfg.NoOverwrite { if err = os.RemoveAll(target); err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error removing target directory: %w", err) } } @@ -97,10 +150,24 @@ The generated folder will be created starting from a provided template and the p cfg.DryRun, ) if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error creating template model: %w", err) + } + + err = templateModel.Generate() + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error generating from template: %w", err) } - return templateModel.Generate() + cmdEvent.AddSuccessMessage("Distribution template generated successfully") + tracker.Track(cmdEvent) + + return nil }, } diff --git a/cmd/root.go b/cmd/root.go index 56bc5e64c..4c0faa811 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,9 @@ package cmd import ( + "fmt" + "os" + "path/filepath" "time" "github.com/briandowns/spinner" @@ -14,9 +17,11 @@ import ( "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/cobrax" - "github.com/sighupio/furyctl/internal/io" "github.com/sighupio/furyctl/internal/semver" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + logrusx "github.com/sighupio/furyctl/internal/x/logrus" ) type rootConfig struct { @@ -24,6 +29,8 @@ type rootConfig struct { Debug bool DisableAnalytics bool DisableTty bool + Workdir string + Log string } type RootCommand struct { @@ -31,10 +38,16 @@ type RootCommand struct { config *rootConfig } -func NewRootCommand(versions map[string]string) *RootCommand { - // Update channels +const ( + timeout = 100 * time.Millisecond + spinnerStyle = 11 +) + +func NewRootCommand(versions map[string]string, logFile *os.File, tracker *analytics.Tracker) *RootCommand { + // Update channels. r := make(chan app.Release, 1) e := make(chan error, 1) + // Analytics event channel. cfg := &rootConfig{} rootCmd := &RootCommand{ @@ -51,38 +64,82 @@ Furyctl is a simple CLI tool to: SilenceUsage: true, SilenceErrors: true, PersistentPreRun: func(cmd *cobra.Command, _ []string) { - // Async check for updates + var err error + + // Async check for updates. go checkUpdates(versions["version"], r, e) - // Configure the spinner + // Configure the spinner. w := logrus.StandardLogger().Out - if cobrax.Flag[bool](cmd, "no-tty").(bool) { - w = io.NewNullWriter() + + cflag, ok := cobrax.Flag[bool](cmd, "no-tty").(bool) + if ok && cflag { + w = iox.NewNullWriter() f := new(logrus.TextFormatter) f.DisableColors = true logrus.SetFormatter(f) } - cfg.Spinner = spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(w)) - // Set log level - if cobrax.Flag[bool](cmd, "debug").(bool) { - logrus.SetLevel(logrus.DebugLevel) - } else { - logrus.SetLevel(logrus.InfoLevel) + cfg.Spinner = spinner.New(spinner.CharSets[spinnerStyle], timeout, spinner.WithWriter(w)) + + logPath, ok := cobrax.Flag[string](cmd, "log").(string) + if ok && logPath != "stdout" { + if logPath == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + logrus.Fatalf("error while getting user home directory: %v", err) + } + + logPath = filepath.Join(homeDir, ".furyctl", "furyctl.log") + } + + logFile, err = createLogFile(logPath) + if err != nil { + logrus.Fatalf("%v", err) + } + + execx.LogFile = logFile + } + + // Set log level. + dflag, ok := cobrax.Flag[bool](cmd, "debug").(bool) + if ok { + logrusx.InitLog(logFile, dflag) } - // Configure analytics - analytics.Version(versions["version"]) - analytics.Disable(cobrax.Flag[bool](cmd, "disable-analytics").(bool)) + logrus.Debugf("logging to: %s", logPath) + + // Configure analytics. + aflag, ok := cobrax.Flag[bool](cmd, "disable-analytics").(bool) + if ok && aflag { + tracker.Disable() + } + + // Change working directory if it is specified. + if workdir, ok := cobrax.Flag[string](cmd, "workdir").(string); workdir != "" && ok { + // Get absolute path of workdir. + absWorkdir, err := filepath.Abs(workdir) + if err != nil { + logrus.Fatalf("Error getting absolute path of workdir: %v", err) + } + + if err := os.Chdir(absWorkdir); err != nil { + logrus.Fatalf("Could not change directory: %v", err) + } + + logrus.Debugf("Changed working directory to %s", absWorkdir) + } }, PersistentPostRun: func(_ *cobra.Command, _ []string) { - // Show update message if available at the end of the command + // Show update message if available at the end of the command. select { case release := <-r: if shouldUpgrade(release.Version, versions["version"]) { logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) } case err := <-e: - logrus.Debugf("Error checking for updates: %s", err) + if err != nil { + logrus.Debugf("Error checking for updates: %s", err) + } } }, }, @@ -92,15 +149,49 @@ Furyctl is a simple CLI tool to: viper.AutomaticEnv() viper.SetEnvPrefix("furyctl") - rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.Debug, "debug", "D", false, "Enables furyctl debug output") - rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.DisableAnalytics, "disable", "d", false, "Disable analytics") - rootCmd.PersistentFlags().BoolVarP(&rootCmd.config.DisableTty, "no-tty", "T", false, "Disable TTY") - - rootCmd.AddCommand(NewCompletionCmd()) - rootCmd.AddCommand(NewDownloadCmd(versions["version"])) - rootCmd.AddCommand(NewDumpCmd()) - rootCmd.AddCommand(NewValidateCommand(versions["version"])) - rootCmd.AddCommand(NewVersionCmd(versions)) + rootCmd.PersistentFlags().BoolVarP( + &rootCmd.config.Debug, + "debug", + "D", + false, + "Enables furyctl debug output", + ) + rootCmd.PersistentFlags().BoolVarP( + &rootCmd.config.DisableAnalytics, + "disable-analytics", + "d", + false, + "Disable analytics", + ) + rootCmd.PersistentFlags().BoolVarP( + &rootCmd.config.DisableTty, + "no-tty", + "T", + false, + "Disable TTY", + ) + rootCmd.PersistentFlags().StringVarP( + &rootCmd.config.Workdir, + "workdir", + "w", + "", + "Switch to a different working directory before executing the given subcommand.", + ) + rootCmd.PersistentFlags().StringVarP( + &rootCmd.config.Log, + "log", + "l", + "", + "Path to the log file or stdout to log to standard output (default: ~/.furyctl/furyctl.log)", + ) + + rootCmd.AddCommand(NewCompletionCmd(tracker)) + rootCmd.AddCommand(NewCreateCommand(tracker)) + rootCmd.AddCommand(NewDownloadCmd(tracker)) + rootCmd.AddCommand(NewDumpCmd(tracker)) + rootCmd.AddCommand(NewValidateCommand(tracker)) + rootCmd.AddCommand(NewVersionCmd(versions, tracker)) + rootCmd.AddCommand(NewDeleteCommand(tracker)) return rootCmd } @@ -119,14 +210,30 @@ func checkUpdates(version string, rc chan app.Release, e chan error) { if version == "unknown" { rc <- app.Release{Version: version} + return } r, err := app.GetLatestRelease() if err != nil { e <- err + return } rc <- r } + +func createLogFile(path string) (*os.File, error) { + // Create the log directory if it doesn't exist. + if err := os.MkdirAll(filepath.Dir(path), iox.UserGroupPerm); err != nil { + return nil, fmt.Errorf("error while creating log file: %w", err) + } + + logFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, iox.RWPermAccess) + if err != nil { + return nil, fmt.Errorf("error while creating log file: %w", err) + } + + return logFile, nil +} diff --git a/cmd/validate.go b/cmd/validate.go index e7c34881e..a6bf38420 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -8,16 +8,17 @@ import ( "github.com/spf13/cobra" "github.com/sighupio/furyctl/cmd/validate" + "github.com/sighupio/furyctl/internal/analytics" ) -func NewValidateCommand(furyctlBinVersion string) *cobra.Command { +func NewValidateCommand(tracker *analytics.Tracker) *cobra.Command { validateCmd := &cobra.Command{ Use: "validate", Short: "Validate fury config files and dependencies", } - validateCmd.AddCommand(validate.NewConfigCmd(furyctlBinVersion)) - validateCmd.AddCommand(validate.NewDependenciesCmd(furyctlBinVersion)) + validateCmd.AddCommand(validate.NewConfigCmd(tracker)) + validateCmd.AddCommand(validate.NewDependenciesCmd(tracker)) return validateCmd } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 846f43563..b21afdaec 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -5,48 +5,76 @@ package validate import ( + "errors" "fmt" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/cobrax" - "github.com/sighupio/furyctl/internal/netx" + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/distribution" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + netx "github.com/sighupio/furyctl/internal/x/net" ) -var ErrValidationFailed = fmt.Errorf("validation failed") +var ( + ErrValidationFailed = fmt.Errorf("config validation failed") + ErrParsingFlag = errors.New("error while parsing flag") +) + +func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event -func NewConfigCmd(furyctlBinVersion string) *cobra.Command { cmd := &cobra.Command{ Use: "config", Short: "Validate furyctl.yaml file", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, RunE: func(cmd *cobra.Command, _ []string) error { - debug := cobrax.Flag[bool](cmd, "debug").(bool) - furyctlPath := cobrax.Flag[string](cmd, "config").(string) - distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: config", ErrParsingFlag) + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: distro-location", ErrParsingFlag) + } - vc := app.NewValidateConfig(netx.NewGoGetterClient()) + dloader := distribution.NewDownloader(netx.NewGoGetterClient()) - res, err := vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: furyctlBinVersion, - DistroLocation: distroLocation, - FuryctlConfPath: furyctlPath, - Debug: debug, - }) + // Download the distribution. + logrus.Info("Downloading distribution...") + res, err := dloader.Download(distroLocation, furyctlPath) if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("failed to download distribution: %w", err) } - if res.HasErrors() { + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + KFDVersion: res.DistroManifest.Version, + }) + + if err := config.Validate(furyctlPath, res.RepoPath); err != nil { logrus.Debugf("Repository path: %s", res.RepoPath) - logrus.Error(res.Error) + logrus.Error(err) + + cmdEvent.AddErrorMessage(ErrValidationFailed) + tracker.Track(cmdEvent) return ErrValidationFailed } - logrus.Info("Config validation succeeded") + logrus.Info("config validation succeeded") + + cmdEvent.AddSuccessMessage("config validation succeeded") + tracker.Track(cmdEvent) return nil }, @@ -65,7 +93,7 @@ func NewConfigCmd(furyctlBinVersion string) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: https://git@github.com/sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 8bbc2d6cf..170488863 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -6,53 +6,109 @@ package validate import ( "fmt" + "os" + "path/filepath" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/cobrax" - "github.com/sighupio/furyctl/internal/execx" - "github.com/sighupio/furyctl/internal/netx" + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/dependencies/envvars" + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/distribution" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" + netx "github.com/sighupio/furyctl/internal/x/net" ) var ErrDependencies = fmt.Errorf("dependencies are not satisfied") -func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { +func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + cmd := &cobra.Command{ Use: "dependencies", Short: "Validate furyctl.yaml file", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, RunE: func(cmd *cobra.Command, _ []string) error { - debug := cobrax.Flag[bool](cmd, "debug").(bool) - binPath := cobrax.Flag[string](cmd, "bin-path").(string) - furyctlPath := cobrax.Flag[string](cmd, "config").(string) - distroLocation := cobrax.Flag[string](cmd, "distro-location").(string) - - vd := app.NewValidateDependencies(netx.NewGoGetterClient(), execx.NewStdExecutor()) - - res, err := vd.Execute(app.ValidateDependenciesRequest{ - BinPath: binPath, - FuryctlBinVersion: furyctlBinVersion, - DistroLocation: distroLocation, - FuryctlConfPath: furyctlPath, - Debug: debug, - }) + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: config", ErrParsingFlag) + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: distro-location", ErrParsingFlag) + } + + dloader := distribution.NewDownloader(netx.NewGoGetterClient()) + + dres, err := dloader.Download(distroLocation, furyctlPath) if err != nil { - return err + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("failed to download distribution: %w", err) } - if res.HasErrors() { - logrus.Debugf("Repository path: %s", res.RepoPath) + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + KFDVersion: dres.DistroManifest.Version, + }) + + binPath := cobrax.Flag[string](cmd, "bin-path").(string) //nolint:errcheck,forcetypeassert // optional flag + if binPath == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("error while getting user home directory: %w", err) + } - for _, err := range res.Errors { + binPath = filepath.Join(homeDir, ".furyctl", "bin") + } + + toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath) + envVarsValidator := envvars.NewValidator() + errs := make([]error, 0) + + toks, terrs := toolsValidator.Validate(dres.DistroManifest) + eoks, eerrs := envVarsValidator.Validate(dres.MinimalConf.Kind) + + errs = append(errs, terrs...) + errs = append(errs, eerrs...) + + for _, tok := range toks { + logrus.Infof("%s: binary found in vendor folder", tok) + } + + for _, eok := range eoks { + logrus.Infof("%s: environment variable found", eok) + } + + if len(errs) > 0 { + logrus.Debugf("Repository path: %s", dres.RepoPath) + + for _, err := range errs { logrus.Error(err) } + cmdEvent.AddErrorMessage(ErrDependencies) + tracker.Track(cmdEvent) + + logrus.Info( + "You can use the 'furyctl download dependencies' command to download most dependencies, " + + "and a package manager such as 'asdf' to install the other ones.", + ) + return ErrDependencies } logrus.Info("Dependencies validation succeeded") + cmdEvent.AddSuccessMessage("Dependencies validation succeeded") + tracker.Track(cmdEvent) + return nil }, } @@ -77,7 +133,7 @@ func NewDependenciesCmd(furyctlBinVersion string) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: https://git@github.com/sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/version.go b/cmd/version.go index 63c30b78b..eac1ca1f3 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -8,16 +8,33 @@ import ( "fmt" "github.com/spf13/cobra" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + + "github.com/sighupio/furyctl/internal/analytics" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" ) -func NewVersionCmd(versions map[string]string) *cobra.Command { +func NewVersionCmd(versions map[string]string, tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + return &cobra.Command{ Use: "version", Short: "Print the version number of furyctl", + PreRun: func(cmd *cobra.Command, args []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, Run: func(_ *cobra.Command, _ []string) { - for k, v := range versions { - fmt.Printf("%s: %s\n", k, v) + keys := maps.Keys(versions) + + slices.Sort(keys) + + for _, k := range keys { + fmt.Printf("%s: %s\n", k, versions[k]) } + + cmdEvent.AddSuccessMessage("version command executed successfully") + tracker.Track(cmdEvent) }, } } diff --git a/internal/distribution/resources.go b/configs/main.go similarity index 69% rename from internal/distribution/resources.go rename to configs/main.go index e69176e61..063018a73 100644 --- a/internal/distribution/resources.go +++ b/configs/main.go @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package distribution +package configs -const EKSCluster Kind = "EKSCluster" +import "embed" + +//go:embed provisioners +var Tpl embed.FS diff --git a/configs/provisioners/bootstrap/aws/main.tf b/configs/provisioners/bootstrap/aws/main.tf deleted file mode 100644 index a77559e04..000000000 --- a/configs/provisioners/bootstrap/aws/main.tf +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -terraform { - required_version = "~> 0.15.4" - required_providers { - aws = "~> 3.56.0" - external = "~> 2.0.0" - local = "~> 2.0.0" - null = "~> 3.0.0" - } -} - -module "vpc-and-vpn" { - source = "github.com/sighupio/fury-eks-installer//modules/vpc-and-vpn?ref=v1.11.0" - - name = var.name - network_cidr = var.network_cidr - public_subnetwork_cidrs = var.public_subnetwork_cidrs - private_subnetwork_cidrs = var.private_subnetwork_cidrs - vpn_subnetwork_cidr = var.vpn_subnetwork_cidr - vpn_port = var.vpn_port - vpn_instances = var.vpn_instances - vpn_instance_type = var.vpn_instance_type - vpn_instance_disk_size = var.vpn_instance_disk_size - vpn_operator_name = var.vpn_operator_name - vpn_dhparams_bits = var.vpn_dhparams_bits - vpn_operator_cidrs = var.vpn_operator_cidrs - vpn_ssh_users = var.vpn_ssh_users - tags = var.tags -} diff --git a/configs/provisioners/cluster/eks/main.tf b/configs/provisioners/cluster/eks/main.tf deleted file mode 100644 index b35135116..000000000 --- a/configs/provisioners/cluster/eks/main.tf +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -terraform { - experiments = [module_variable_optional_attrs] - required_version = "~> 0.15.4" - required_providers { - aws = "~> 3.56.0" - kubernetes = "~> 1.13.3" - local = "~> 1.4.0" - null = "~> 2.1.0" - random = "~> 2.1.0" - template = "~> 2.1.0" - } -} - -module "fury" { - source = "github.com/sighupio/fury-eks-installer//modules/eks?ref=v1.11.0" - - cluster_name = var.cluster_name - cluster_version = var.cluster_version - cluster_log_retention_days = var.cluster_log_retention_days - network = var.network - subnetworks = var.subnetworks - dmz_cidr_range = var.dmz_cidr_range - ssh_public_key = var.ssh_public_key - node_pools = var.node_pools - node_pools_launch_kind = var.node_pools_launch_kind - tags = var.tags - - # Specific AWS variables. - # Enables managing auth using these variables - eks_map_users = var.eks_map_users - eks_map_roles = var.eks_map_roles - eks_map_accounts = var.eks_map_accounts -} diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl new file mode 100644 index 000000000..fa43e46c3 --- /dev/null +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +terraform { + experiments = [module_variable_optional_attrs] + + backend "s3" { + bucket = "{{ .terraform.backend.s3.bucketName }}" + key = "{{ .terraform.backend.s3.keyPrefix }}/cluster.json" + region = "{{ .terraform.backend.s3.region }}" + } +} + +module "fury" { + source = "github.com/sighupio/fury-eks-installer//modules/eks?ref={{ .kubernetes.eks.installer }}" + + cluster_name = var.cluster_name + cluster_version = var.cluster_version + network = var.network + subnetworks = var.subnetworks + dmz_cidr_range = var.dmz_cidr_range + ssh_public_key = var.ssh_public_key + node_pools = var.node_pools + tags = var.tags + + # Specific AWS variables. + # Enables managing auth using these variables + eks_map_users = var.eks_map_users + eks_map_roles = var.eks_map_roles + eks_map_accounts = var.eks_map_accounts +} diff --git a/configs/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf index eccbd14dd..32ea9eee7 100644 --- a/configs/provisioners/cluster/eks/variables.tf +++ b/configs/provisioners/cluster/eks/variables.tf @@ -11,7 +11,7 @@ variable "cluster_name" { variable "cluster_version" { type = string - description = "Kubernetes Cluster Version. Look at the cloud providers documentation to discover available versions. EKS example -> 1.23" + description = "Kubernetes Cluster Version. Look at the cloud providers documentation to discover available versions. EKS example -> 1.24" } variable "cluster_log_retention_days" { diff --git a/go.mod b/go.mod index 5571193fc..22565c1c7 100644 --- a/go.mod +++ b/go.mod @@ -3,101 +3,92 @@ module github.com/sighupio/furyctl go 1.19 require ( - github.com/briandowns/spinner v1.12.0 + github.com/Masterminds/sprig/v3 v3.2.3 + github.com/briandowns/spinner v1.19.0 github.com/denisbrodbeck/machineid v1.0.1 - github.com/dukex/mixpanel v0.0.0-20180925151559-f8d5594f958e - github.com/fatih/color v1.10.0 // indirect - github.com/gobuffalo/logger v1.0.7 // indirect - github.com/gobuffalo/packd v1.0.2 // indirect - github.com/gobuffalo/packr/v2 v2.8.3 - github.com/hashicorp/go-checkpoint v0.5.0 + github.com/dukex/mixpanel v1.0.1 github.com/hashicorp/go-getter v1.6.2 - github.com/hashicorp/go-version v1.3.0 - github.com/hashicorp/terraform-exec v0.13.3 - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/karrick/godirwalk v1.17.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 - github.com/relex/aini v1.2.1 + github.com/hashicorp/go-version v1.6.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.8.1 - github.com/stretchr/testify v1.8.0 - golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 - golang.org/x/term v0.5.0 // indirect - golang.org/x/tools v0.5.0 // indirect + github.com/spf13/viper v1.14.0 + github.com/stretchr/testify v1.8.1 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/google/go-cmp v0.5.8 + github.com/go-playground/validator/v10 v10.11.1 + github.com/google/go-cmp v0.5.9 + github.com/hashicorp/terraform-json v0.14.0 + github.com/onsi/ginkgo/v2 v2.6.1 + github.com/onsi/gomega v1.24.2 + github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 + github.com/sighupio/fury-distribution v1.24.1-0.20230103153037-ad0c881984b3 + golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) require ( - cloud.google.com/go v0.81.0 // indirect - cloud.google.com/go/storage v1.10.0 // indirect + cloud.google.com/go v0.107.0 // indirect + cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v0.9.0 // indirect + cloud.google.com/go/storage v1.28.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect - github.com/aws/aws-sdk-go v1.27.0 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/aws/aws-sdk-go v1.44.163 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.10.0 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/gobuffalo/logger v1.0.7 // indirect - github.com/gobuffalo/packd v1.0.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect + github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-uuid v1.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/terraform-json v0.10.0 // indirect - github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect - github.com/jstemmer/go-junit-report v0.9.1 // indirect - github.com/karrick/godirwalk v1.17.0 // indirect - github.com/klauspost/compress v1.11.2 // indirect - github.com/magiconair/properties v1.8.5 // indirect - github.com/markbates/errx v1.1.0 // indirect - github.com/markbates/oncer v1.0.0 // indirect - github.com/markbates/safe v1.0.1 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.15.13 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/mitchellh/reflectwalk v1.0.0 // indirect - github.com/pelletier/go-toml v1.9.3 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.3.1 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.2.0 // indirect - github.com/ulikunitz/xz v0.5.8 // indirect - github.com/zclconf/go-cty v1.8.2 // indirect - go.opencensus.io v0.23.0 // indirect - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect - golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect - golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.12 // indirect - google.golang.org/api v0.44.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/zclconf/go-cty v1.12.1 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/crypto v0.4.0 // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/oauth2 v0.3.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.105.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect - google.golang.org/grpc v1.38.0 // indirect - google.golang.org/protobuf v1.26.0 // indirect - gopkg.in/ini.v1 v1.62.0 // indirect + google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect + google.golang.org/grpc v1.51.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 3cf1033d1..4175afaaf 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,19 +16,24 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/iam v0.9.0 h1:bK6Or6mxhuL8lnj1i9j0yMo2wE/IeTO2cWlfUrf/TZs= +cloud.google.com/go/iam v0.9.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -36,42 +42,28 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= -github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.163 h1:XO1A/Laqf/l0IxVPghaQzdnVwxofVFv00IlX0BpmbhQ= +github.com/aws/aws-sdk-go v1.44.163/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw= -github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= +github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= +github.com/briandowns/spinner v1.19.0/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -81,9 +73,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -92,51 +81,39 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= -github.com/dukex/mixpanel v0.0.0-20180925151559-f8d5594f958e h1:Rr/xguBo8FlFC/U8ekbeWDBYydqZDD6bKGT5rDlBCUU= -github.com/dukex/mixpanel v0.0.0-20180925151559-f8d5594f958e/go.mod h1:AgMMmOoSoKDavirJHvIHNcaPq2S9QvZKnuN0We/Hwyo= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/dukex/mixpanel v1.0.1 h1:IQ3qBjtgltF044jU9+i6MubdDdpc8PKpK9yvfawRgeE= +github.com/dukex/mixpanel v1.0.1/go.mod h1:080BDsRRMzAxViWT3OjlQaMW9nhaIEXDHHtGeDK60b8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.1.0 h1:4pl5BV4o7ZG/lterP4S6WzJ6xr49Ba5ET9ygheTYahk= -github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= -github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= -github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/logger v1.0.7 h1:LTLwWelETXDYyqF/ASf0nxaIcdEOIJNxRokPcfI/xbU= -github.com/gobuffalo/logger v1.0.7/go.mod h1:u40u6Bq3VVvaMcy5sRBclD8SXhBYPS0Qk95ubt+1xJM= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw= -github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -144,7 +121,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -161,7 +137,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -177,14 +152,14 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -194,194 +169,143 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= -github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI= github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk= github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= -github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/terraform-exec v0.13.3 h1:R6L2mNpDGSEqtLrSONN8Xth0xYwNrnEVzDz6LF/oJPk= -github.com/hashicorp/terraform-exec v0.13.3/go.mod h1:SSg6lbUsVB3DmFyCPjBPklqf6EYGX0TlQ6QTxOlikDU= -github.com/hashicorp/terraform-json v0.10.0 h1:9syPD/Y5t+3uFjG8AiWVPu1bklJD8QB8iTCaJASc8oQ= -github.com/hashicorp/terraform-json v0.10.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= +github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= -github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= +github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= -github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= +github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= -github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= +github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sighupio/fury-distribution v1.24.1-0.20230103153037-ad0c881984b3 h1:ypsKhdTtCEI4+N4MYWCcbm1rcf4gdUb+Yyrc0cqNySk= +github.com/sighupio/fury-distribution v1.24.1-0.20230103153037-ad0c881984b3/go.mod h1:MpW/zGMlwJIQoBgZ6xWwL6RN6j7N5bCEX8949p3Pfik= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -389,55 +313,47 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.8.2 h1:u+xZfBKgpycDnTNjPhGiTEYZS5qS/Sb5MqSfm7vzcjg= -github.com/zclconf/go-cty v1.8.2/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= +github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -448,6 +364,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w= +golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -461,8 +379,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -473,14 +389,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -509,14 +421,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -526,10 +438,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= +golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -540,13 +450,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -556,10 +461,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -582,25 +484,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= -golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -610,8 +513,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -621,7 +525,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -631,7 +534,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -654,7 +556,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -663,16 +564,15 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -692,10 +592,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= +google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -727,7 +625,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -739,13 +636,10 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -759,14 +653,11 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -778,29 +669,25 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go deleted file mode 100644 index 06a30564f..000000000 --- a/internal/analytics/analytics.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package analytics - -import ( - "net" - "net/http" - "runtime" - "time" - - "github.com/denisbrodbeck/machineid" - "github.com/dukex/mixpanel" - "github.com/sirupsen/logrus" -) - -const ( - mixpanelToken = "07964a709c19657ded1d402e5f5469b2" - - bootstrapInitEvent = "BootstrapInit" - bootstrapApplyEvent = "BootstrapApply" - bootstrapDestroyEvent = "BootstrapDestroy" - clusterInitEvent = "ClusterInit" - clusterApplyEvent = "ClusterApply" - clusterDestroyEvent = "ClusterDestroy" -) - -var ( - mixpanelClient mixpanel.Mixpanel - disable bool - version string -) - -func init() { - c := &http.Client{ - Timeout: time.Second * 5, - Transport: &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 5 * time.Second, - }).Dial, - TLSHandshakeTimeout: 5 * time.Second, - }, - } - - mixpanelClient = mixpanel.NewFromClient(c, mixpanelToken, "https://api-eu.mixpanel.com") -} - -func enabled() bool { - return !disable -} - -// Version will set the version of the CLI -func Version(v string) { - version = v -} - -// Disable will disable analytics -func Disable(d bool) { - disable = d -} - -func track(event string, success bool, token string, props map[string]interface{}) { - if enabled() { - mpOS := "" - switch runtime.GOOS { - case "darwin": - mpOS = "Mac OS X" - case "windows": - mpOS = "Windows" - case "linux": - mpOS = "Linux" - } - - origin := "furyctl" - - if props == nil { - props = map[string]interface{}{} - } - - props["$os"] = mpOS - props["version"] = version - props["origin"] = origin - props["success"] = success - - e := &mixpanel.Event{Properties: props} - trackID := getTrackID(token) - if err := mixpanelClient.Track(trackID, event, e); err != nil { - logrus.WithError(err).Debugf("Failed to send analytics: %s", err) - } - } else { - logrus.Debugf("not sending event for %s", event) - } -} - -func getTrackID(token string) string { - if token != "" { - return token - } - return generateMachineID() -} - -func generateMachineID() string { - mid, err := machineid.ProtectedID("furyctl") - if err != nil { - logrus.WithError(err).Debug("failed to generate a machine id") - mid = "na" - } - - return mid -} - -// TrackBootstrapInit sends a tracking event to mixpanel when the user uses the bootstrap init command -func TrackBootstrapInit(token string, success bool, provisioner string) { - props := map[string]interface{}{ - "provisioner": provisioner, - "githubToken": token, - } - track(bootstrapInitEvent, success, token, props) -} - -// TrackBootstrapApply sends a tracking event to mixpanel when the user uses the bootstrap update command -func TrackBootstrapApply(token string, success bool, provisioner string, dryRun bool) { - props := map[string]interface{}{ - "provisioner": provisioner, - "dryRun": dryRun, - "githubToken": token, - } - track(bootstrapApplyEvent, success, token, props) -} - -// TrackBootstrapDestroy sends a tracking event to mixpanel when the user uses the bootstrap destroy command -func TrackBootstrapDestroy(token string, success bool, provisioner string) { - props := map[string]interface{}{ - "provisioner": provisioner, - "githubToken": token, - } - track(bootstrapDestroyEvent, success, token, props) -} - -// TrackClusterInit sends a tracking event to mixpanel when the user uses the cluster init command -func TrackClusterInit(token string, success bool, provisioner string) { - props := map[string]interface{}{ - "provisioner": provisioner, - "githubToken": token, - } - track(clusterInitEvent, success, token, props) -} - -// TrackClusterApply sends a tracking event to mixpanel when the user uses the cluster update command -func TrackClusterApply(token string, success bool, provisioner string, dryRun bool) { - props := map[string]interface{}{ - "provisioner": provisioner, - "dryRun": dryRun, - "githubToken": token, - } - track(clusterApplyEvent, success, token, props) -} - -// TrackClusterDestroy sends a tracking event to mixpanel when the user uses the cluster destroy command -func TrackClusterDestroy(token string, success bool, provisioner string) { - props := map[string]interface{}{ - "provisioner": provisioner, - "githubToken": token, - } - track(clusterDestroyEvent, success, token, props) -} diff --git a/internal/analytics/event.go b/internal/analytics/event.go new file mode 100644 index 000000000..cb40fa803 --- /dev/null +++ b/internal/analytics/event.go @@ -0,0 +1,98 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package analytics + +type Event interface { + Properties() map[string]interface{} + AddErrorMessage(error) + AddSuccessMessage(string) + AddClusterDetails(c ClusterDetails) + AddExitCode(int) + Name() string +} + +func NewCommandEvent(name string) Event { + return CommandEvent{ + name: name, + properties: make(map[string]interface{}), + } +} + +func (c CommandEvent) AddErrorMessage(e error) { + c.properties["errorMessage"] = e.Error() + c.properties["success"] = false +} + +func (c CommandEvent) AddSuccessMessage(msg string) { + c.properties["successMessage"] = msg + c.properties["success"] = true +} + +func (c CommandEvent) AddClusterDetails(d ClusterDetails) { + c.properties["clusterDetails"] = d +} + +func (c CommandEvent) AddExitCode(e int) { + c.properties["exitCode"] = e +} + +func (c CommandEvent) Properties() map[string]interface{} { + return c.properties +} + +func (c CommandEvent) Name() string { + return c.name +} + +// NewStopEvent creates a new StopEvent. StopEvent is a special type of event used to close the events processing. +func NewStopEvent() Event { + return StopEvent{ + name: "stop", + properties: make(map[string]interface{}), + } +} + +func (g StopEvent) AddErrorMessage(e error) { + g.properties["errorMessage"] = e.Error() +} + +func (g StopEvent) AddSuccessMessage(msg string) { + g.properties["successMessage"] = msg +} + +func (g StopEvent) AddClusterDetails(d ClusterDetails) { + g.properties["clusterDetails"] = d +} + +func (g StopEvent) AddExitCode(e int) { + g.properties["exitCode"] = e +} + +func (g StopEvent) Properties() map[string]interface{} { + return g.properties +} + +func (g StopEvent) Name() string { + return g.name +} + +// StopEvent is a special event used to close the events processing. +type StopEvent struct { + name string + properties map[string]interface{} +} + +type CommandEvent struct { + name string + properties +} + +type properties map[string]interface{} + +type ClusterDetails struct { + Phase string + Provider string + KFDVersion string +} diff --git a/internal/analytics/tracker.go b/internal/analytics/tracker.go new file mode 100644 index 000000000..1ab9de25a --- /dev/null +++ b/internal/analytics/tracker.go @@ -0,0 +1,176 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package analytics + +import ( + "fmt" + "net" + "net/http" + "time" + + "github.com/denisbrodbeck/machineid" + "github.com/dukex/mixpanel" + "github.com/sirupsen/logrus" +) + +// NewTracker returns a new Tracker instance. +func NewTracker(token, version, arch, os, org, hostname string) *Tracker { + const timeout = time.Second * 5 + + const apiEndpoint = "https://api-eu.mixpanel.com" + + c := &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: timeout, + }).Dial, + TLSHandshakeTimeout: timeout, + }, + } + + t := map[string]string{ + "version": version, + "origin": "furyctl", + "architecture": arch, + "$os": os, + "org": org, + "hostname": hostname, + "trackID": getTrackID(token), + } + + tracker := &Tracker{ + client: mixpanel.NewFromClient(c, token, apiEndpoint), + trackingInfo: t, + enable: true, + events: make(chan Event), + } + + if token == "" { + tracker.enable = false + } + + // Start the event processor, this will listen for new tracked events and send them to mixpanel. + go tracker.processEvents() + + return tracker +} + +type Tracker struct { + trackingInfo + client mixpanel.Mixpanel + events chan Event + enable bool +} + +type trackingInfo map[string]string + +// Track collects the event to be consumed by the event processor. +func (a *Tracker) Track(event Event) { + // // add a channel to send events to a goroutine that will send them to mixpanel + // // this will allow us to send events in a non-blocking way. + if a.enable { + a.events <- event + } +} + +// Flush flushes the events queue, guaranteeing that all events are sent to mixpanel. +// This method uses a timeout to send a GuardEvent to the event processor to close the process. +func (a *Tracker) Flush() { + const timeout = time.Millisecond * 500 + + go func() { + time.Sleep(timeout) + a.events <- NewStopEvent() + }() + + a.processEvents() + + logrus.Debug("Flushed events queue") +} + +// processEvents is the event processor: it will listen for new events and send them to mixpanel. +// This method will stop when a Stop event is received. +func (a *Tracker) processEvents() { + for { + e, ok := <-a.events + if !ok { + logrus.Debug("Event processor stopped") + + break + } + + logrus.Debug("Processing event: ", e.Name()) + + switch e.(type) { + case StopEvent: + logrus.Debug("Stop event received, stopping event processor") + a.close() + + return + + case CommandEvent: + logrus.Debug("Sending event: ", e.Name()) + + if err := a.sendEvent(e); err != nil { + logrus.WithError(err).Error("failed to send event") + } + + logrus.Debug("Event sent: ", e.Name()) + } + } +} + +// sendEvent sends the event to mixpanel. +func (a *Tracker) sendEvent(event Event) error { + // Event Properties with machine info. + p := appendMachineInfo(a.trackingInfo, event.Properties()) + + e := &mixpanel.Event{Properties: p} + if err := a.client.Track(a.trackingInfo["trackID"], event.Name(), e); err != nil { + return fmt.Errorf("failed to track event: %w", err) + } + + return nil +} + +// Disable disables the tracker. +func (a *Tracker) Disable() { + a.enable = false + + a.close() +} + +// close closes the tracker's event chan. +func (a *Tracker) close() { + close(a.events) +} + +func getTrackID(token string) string { + if token != "" { + return token + } + + return generateMachineID() +} + +func generateMachineID() string { + mid, err := machineid.ProtectedID("furyctl") + if err != nil { + logrus.WithError(err).Debug("failed to generate a machine id") + + mid = "na" + } + + return mid +} + +func appendMachineInfo(src map[string]string, dst map[string]interface{}) map[string]interface{} { + for k, v := range src { + dst[k] = v + } + + return dst +} diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go new file mode 100644 index 000000000..75ccab5d7 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -0,0 +1,605 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package create + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/template" + "github.com/sighupio/furyctl/internal/tool/kubectl" + "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +const ( + kubectlDelayMaxRetry = 3 + kubectlNoDelayMaxRetry = 7 +) + +var ( + errCastingVpcIDToStr = errors.New("error casting vpc_id output to string") + errCastingEbsIamToStr = errors.New("error casting ebs_csi_driver_iam_role_arn output to string") + errCastingLbIamToStr = errors.New("error casting load_balancer_controller_iam_role_arn output to string") + errCastingClsAsIamToStr = errors.New("error casting cluster_autoscaler_iam_role_arn output to string") + errCastingDNSPvtIamToStr = errors.New("error casting external_dns_private_iam_role_arn output to string") + errCastingDNSPubIamToStr = errors.New("error casting external_dns_public_iam_role_arn output to string") + errCastingCertIamToStr = errors.New("error casting cert_manager_iam_role_arn output to string") + errCastingVelIamToStr = errors.New("error casting velero_iam_role_arn output to string") +) + +type Distribution struct { + *cluster.OperationPhase + furyctlConfPath string + furyctlConf schema.EksclusterKfdV1Alpha2 + kfdManifest config.KFD + infraOutputsPath string + distroPath string + tfRunner *terraform.Runner + kzRunner *kustomize.Runner + kubeRunner *kubectl.Runner + dryRun bool +} + +type injectType struct { + Data schema.SpecDistribution `json:"data"` +} + +func NewDistribution( + paths cluster.CreatorPaths, + furyctlConf schema.EksclusterKfdV1Alpha2, + kfdManifest config.KFD, + infraOutputsPath string, + dryRun bool, +) (*Distribution, error) { + distroDir := path.Join(paths.WorkDir, cluster.OperationPhaseDistribution) + + phase, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, paths.BinPath) + if err != nil { + return nil, fmt.Errorf("error creating distribution phase: %w", err) + } + + return &Distribution{ + OperationPhase: phase, + furyctlConf: furyctlConf, + kfdManifest: kfdManifest, + infraOutputsPath: infraOutputsPath, + distroPath: paths.DistroPath, + furyctlConfPath: paths.ConfigPath, + tfRunner: terraform.NewRunner( + execx.NewStdExecutor(), + terraform.Paths{ + Logs: phase.LogsPath, + Outputs: phase.OutputsPath, + WorkDir: path.Join(phase.Path, "terraform"), + Plan: phase.PlanPath, + Terraform: phase.TerraformPath, + }, + ), + kzRunner: kustomize.NewRunner( + execx.NewStdExecutor(), + kustomize.Paths{ + Kustomize: phase.KustomizePath, + WorkDir: path.Join(phase.Path, "manifests"), + }, + ), + kubeRunner: kubectl.NewRunner( + execx.NewStdExecutor(), + kubectl.Paths{ + Kubectl: phase.KubectlPath, + WorkDir: path.Join(phase.Path, "manifests"), + Kubeconfig: paths.Kubeconfig, + }, + true, + true, + ), + dryRun: dryRun, + }, nil +} + +func (d *Distribution) Exec() error { + logrus.Info("Running distribution phase") + + timestamp := time.Now().Unix() + + if err := d.CreateFolder(); err != nil { + return fmt.Errorf("error creating distribution phase folder: %w", err) + } + + furyctlMerger, err := d.createFuryctlMerger() + if err != nil { + return err + } + + preTfMerger, err := d.injectDataPreTf(furyctlMerger) + if err != nil { + return err + } + + tfCfg, err := template.NewConfig(furyctlMerger, preTfMerger, []string{"source/manifests", ".gitignore"}) + if err != nil { + return fmt.Errorf("error creating template config: %w", err) + } + + if err := d.copyFromTemplate(tfCfg); err != nil { + return err + } + + if err := d.CreateFolderStructure(); err != nil { + return fmt.Errorf("error creating distribution phase folder structure: %w", err) + } + + if err := d.tfRunner.Init(); err != nil { + return fmt.Errorf("error running terraform init: %w", err) + } + + if err := d.tfRunner.Plan(timestamp); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + if d.dryRun { + if err := d.createDummyOutput(); err != nil { + return fmt.Errorf("error creating dummy output: %w", err) + } + + postTfMerger, err := d.injectDataPostTf(preTfMerger) + if err != nil { + return err + } + + mCfg, err := template.NewConfig(furyctlMerger, postTfMerger, []string{"source/terraform", ".gitignore"}) + if err != nil { + return fmt.Errorf("error creating template config: %w", err) + } + + if err := d.copyFromTemplate(mCfg); err != nil { + return err + } + + _, err = d.buildManifests() + if err != nil { + return err + } + + return nil + } + + logrus.Info("Creating cloud resources, this could take a while...") + + _, err = d.tfRunner.Apply(timestamp) + if err != nil { + return fmt.Errorf("cannot create cloud resources: %w", err) + } + + postTfMerger, err := d.injectDataPostTf(preTfMerger) + if err != nil { + return err + } + + mCfg, err := template.NewConfig(furyctlMerger, postTfMerger, []string{"source/terraform", ".gitignore"}) + if err != nil { + return fmt.Errorf("error creating template config: %w", err) + } + + if err := d.copyFromTemplate(mCfg); err != nil { + return err + } + + logrus.Info("Building manifests...") + + manifestsOutPath, err := d.buildManifests() + if err != nil { + return err + } + + logrus.Info("Applying manifests...") + + return d.applyManifests(manifestsOutPath) +} + +func (d *Distribution) createFuryctlMerger() (*merge.Merger, error) { + defaultsFilePath := path.Join(d.distroPath, "furyctl-defaults.yaml") + + defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) + if err != nil { + return &merge.Merger{}, fmt.Errorf("%s - %w", defaultsFilePath, err) + } + + furyctlConf, err := yamlx.FromFileV2[map[any]any](d.furyctlConfPath) + if err != nil { + return &merge.Merger{}, fmt.Errorf("%s - %w", d.furyctlConfPath, err) + } + + furyctlConfMergeModel := merge.NewDefaultModel(furyctlConf, ".spec.distribution") + + merger := merge.NewMerger( + merge.NewDefaultModel(defaultsFile, ".data"), + furyctlConfMergeModel, + ) + + _, err = merger.Merge() + if err != nil { + return nil, fmt.Errorf("error merging furyctl config: %w", err) + } + + reverseMerger := merge.NewMerger( + *merger.GetCustom(), + *merger.GetBase(), + ) + + _, err = reverseMerger.Merge() + if err != nil { + return nil, fmt.Errorf("error merging furyctl config: %w", err) + } + + return reverseMerger, nil +} + +func (d *Distribution) injectDataPreTf(fMerger *merge.Merger) (*merge.Merger, error) { + vpcID, err := d.extractVpcIDFromPrevPhases(fMerger) + if err != nil { + return nil, err + } + + if vpcID == "" { + return fMerger, nil + } + + injectData := injectType{ + Data: schema.SpecDistribution{ + Modules: schema.SpecDistributionModules{ + Ingress: schema.SpecDistributionModulesIngress{ + Dns: schema.SpecDistributionModulesIngressDNS{ + Private: schema.SpecDistributionModulesIngressDNSPrivate{ + VpcId: vpcID, + }, + }, + }, + }, + }, + } + + injectDataModel := merge.NewDefaultModelFromStruct(injectData, ".data", true) + + merger := merge.NewMerger( + *fMerger.GetBase(), + injectDataModel, + ) + + _, err = merger.Merge() + if err != nil { + return nil, fmt.Errorf("error merging furyctl config: %w", err) + } + + return merger, nil +} + +func (d *Distribution) extractVpcIDFromPrevPhases(fMerger *merge.Merger) (string, error) { + vpcID := "" + + if infraOutJSON, err := os.ReadFile(path.Join(d.infraOutputsPath, "output.json")); err == nil { + var infraOut terraform.OutputJSON + + if err := json.Unmarshal(infraOutJSON, &infraOut); err == nil { + if infraOut.Outputs["vpc_id"] == nil { + return vpcID, ErrVpcIDNotFound + } + + vpcIDOut, ok := infraOut.Outputs["vpc_id"].Value.(string) + if !ok { + return vpcID, errCastingVpcIDToStr + } + + vpcID = vpcIDOut + } + } else { + fModel := merge.NewDefaultModel((*fMerger.GetBase()).Content(), ".spec.kubernetes") + + kubeFromFuryctlConf, err := fModel.Get() + if err != nil { + return vpcID, fmt.Errorf("error getting kubernetes from furyctl config: %w", err) + } + + vpcFromFuryctlConf, ok := kubeFromFuryctlConf["vpcId"].(string) + if !ok { + return vpcID, errCastingVpcIDToStr + } + + vpcID = vpcFromFuryctlConf + } + + return vpcID, nil +} + +func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, error) { + arns, err := d.extractARNsFromTfOut() + if err != nil { + return nil, err + } + + injectData := injectType{ + Data: schema.SpecDistribution{ + Modules: schema.SpecDistributionModules{ + Aws: &schema.SpecDistributionModulesAws{ + EbsCsiDriver: &schema.SpecDistributionModulesAwsEbsCsiDriver{ + IamRoleArn: schema.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), + }, + LoadBalancerController: &schema.SpecDistributionModulesAwsLoadBalancerController{ + IamRoleArn: schema.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), + }, + ClusterAutoscaler: &schema.SpecDistributionModulesAwsClusterAutoScaler{ + IamRoleArn: schema.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), + }, + }, + Ingress: schema.SpecDistributionModulesIngress{ + ExternalDns: &schema.SpecDistributionModulesIngressExternalDNS{ + PrivateIamRoleArn: schema.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), + PublicIamRoleArn: schema.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), + }, + CertManager: &schema.SpecDistributionModulesIngressCertManager{ + ClusterIssuer: schema.SpecDistributionModulesIngressCertManagerClusterIssuer{ + Route53: &schema.SpecDistributionModulesIngressClusterIssuerRoute53{ + IamRoleArn: schema.TypesAwsArn(arns["cert_manager_iam_role_arn"]), + }, + }, + }, + }, + Dr: schema.SpecDistributionModulesDr{ + Velero: schema.SpecDistributionModulesDrVelero{ + Eks: schema.SpecDistributionModulesDrVeleroEks{ + IamRoleArn: schema.TypesAwsArn(arns["velero_iam_role_arn"]), + }, + }, + }, + }, + }, + } + + injectDataModel := merge.NewDefaultModelFromStruct(injectData, ".data", true) + + merger := merge.NewMerger( + *fMerger.GetBase(), + injectDataModel, + ) + + _, err = merger.Merge() + if err != nil { + return nil, fmt.Errorf("error merging furyctl config: %w", err) + } + + return merger, nil +} + +func (d *Distribution) createDummyOutput() error { + arns := map[string]string{ + "ebs_csi_driver_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + "load_balancer_controller_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + "cluster_autoscaler_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + "external_dns_private_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + "external_dns_public_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + "cert_manager_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + "velero_iam_role_arn": "arn:aws:iam::123456789012:role/dummy", + } + + outputFilePath := path.Join(d.OutputsPath, "output.json") + + if _, err := os.Stat(outputFilePath); err == nil { + return nil + } + + if err := os.MkdirAll(d.OutputsPath, iox.FullPermAccess); err != nil { + return fmt.Errorf("error while creating outputs folder: %w", err) + } + + arnsJSON, err := json.Marshal(arns) + if err != nil { + return fmt.Errorf("error while marshaling arns: %w", err) + } + + if err := os.WriteFile(outputFilePath, arnsJSON, iox.RWPermAccess); err != nil { + return fmt.Errorf("error while creating dummy output.json: %w", err) + } + + return nil +} + +func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { + var distroOut terraform.OutputJSON + + arns := map[string]string{} + + distroOutJSON, err := os.ReadFile(path.Join(d.OutputsPath, "output.json")) + if err != nil { + return nil, fmt.Errorf("error reading distribution output: %w", err) + } + + if err := json.Unmarshal(distroOutJSON, &distroOut); err != nil { + return nil, fmt.Errorf("error unmarshaling distribution output: %w", err) + } + + ebsCsiDriverArn, ok := distroOut.Outputs["ebs_csi_driver_iam_role_arn"] + if ok { + arns["ebs_csi_driver_iam_role_arn"], ok = ebsCsiDriverArn.Value.(string) + if !ok { + return nil, errCastingEbsIamToStr + } + } + + loadBalancerControllerArn, ok := distroOut.Outputs["load_balancer_controller_iam_role_arn"] + if ok { + arns["load_balancer_controller_iam_role_arn"], ok = loadBalancerControllerArn.Value.(string) + if !ok { + return nil, errCastingLbIamToStr + } + } + + clusterAutoscalerArn, ok := distroOut.Outputs["cluster_autoscaler_iam_role_arn"] + if ok { + arns["cluster_autoscaler_iam_role_arn"], ok = clusterAutoscalerArn.Value.(string) + if !ok { + return nil, errCastingClsAsIamToStr + } + } + + externalDNSPrivateArn, ok := distroOut.Outputs["external_dns_private_iam_role_arn"] + if ok { + arns["external_dns_private_iam_role_arn"], ok = externalDNSPrivateArn.Value.(string) + if !ok { + return nil, errCastingDNSPvtIamToStr + } + } + + externalDNSPublicArn, ok := distroOut.Outputs["external_dns_public_iam_role_arn"] + if ok { + arns["external_dns_public_iam_role_arn"], ok = externalDNSPublicArn.Value.(string) + if !ok { + return nil, errCastingDNSPubIamToStr + } + } + + certManagerArn, ok := distroOut.Outputs["cert_manager_iam_role_arn"] + if ok { + arns["cert_manager_iam_role_arn"], ok = certManagerArn.Value.(string) + if !ok { + return nil, errCastingCertIamToStr + } + } + + veleroArn, ok := distroOut.Outputs["velero_iam_role_arn"] + if ok { + arns["velero_iam_role_arn"], ok = veleroArn.Value.(string) + if !ok { + return nil, errCastingVelIamToStr + } + } + + return arns, nil +} + +func (d *Distribution) copyFromTemplate(cfg template.Config) error { + outYaml, err := yamlx.MarshalV2(cfg) + if err != nil { + return fmt.Errorf("error marshaling template config: %w", err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-") + if err != nil { + return fmt.Errorf("error creating temp dir: %w", err) + } + + confPath := filepath.Join(outDirPath, "config.yaml") + + logrus.Debugf("config path = %s", confPath) + + if err = os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return fmt.Errorf("error writing config file: %w", err) + } + + templateModel, err := template.NewTemplateModel( + path.Join(d.distroPath, "templates", cluster.OperationPhaseDistribution), + path.Join(d.Path), + confPath, + outDirPath, + ".tpl", + false, + d.dryRun, + ) + if err != nil { + return fmt.Errorf("error creating template model: %w", err) + } + + err = templateModel.Generate() + if err != nil { + return fmt.Errorf("error generating from template files: %w", err) + } + + return nil +} + +func (d *Distribution) buildManifests() (string, error) { + kzOut, err := d.kzRunner.Build() + if err != nil { + return "", fmt.Errorf("error building manifests: %w", err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") + if err != nil { + return "", fmt.Errorf("error creating temp dir: %w", err) + } + + manifestsOutPath := filepath.Join(outDirPath, "out.yaml") + + logrus.Debugf("built manifests = %s", manifestsOutPath) + + if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { + return "", fmt.Errorf("error writing built manifests: %w", err) + } + + return manifestsOutPath, nil +} + +func (d *Distribution) applyManifests(mPath string) error { + err := d.delayedApplyRetries(mPath, time.Minute, kubectlDelayMaxRetry) + if err == nil { + return nil + } + + err = d.delayedApplyRetries(mPath, 0, kubectlNoDelayMaxRetry) + if err == nil { + return nil + } + + return err +} + +func (d *Distribution) delayedApplyRetries(mPath string, delay time.Duration, maxRetries int) error { + var err error + + retries := 0 + + if maxRetries == 0 { + return nil + } + + err = d.kubeRunner.Apply(mPath) + if err == nil { + return nil + } + + retries++ + + for retries < maxRetries { + t := time.NewTimer(delay) + + if <-t.C; true { + logrus.Debug("applying manifests again... to ensure all resources are created.") + + err = d.kubeRunner.Apply(mPath) + if err == nil { + return nil + } + } + + retries++ + + t.Stop() + } + + return fmt.Errorf("error applying manifests: %w", err) +} diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go new file mode 100644 index 000000000..33b5a3d6c --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -0,0 +1,459 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package create + +import ( + "bytes" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/furyctl/configs" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/template" + "github.com/sighupio/furyctl/internal/tool/furyagent" + "github.com/sighupio/furyctl/internal/tool/openvpn" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +const SErrWrapWithStr = "%w: %s" + +var ( + ErrVpcIDNotFound = errors.New("vpc_id not found in infra output") + ErrVpcIDFromOut = errors.New("cannot read vpc_id from infrastructure's output.json") + ErrWritingTfVars = errors.New("error writing terraform variables file") +) + +type Infrastructure struct { + *cluster.OperationPhase + furyctlConf schema.EksclusterKfdV1Alpha2 + kfdManifest config.KFD + tfRunner *terraform.Runner + faRunner *furyagent.Runner + ovRunner *openvpn.Runner + dryRun bool +} + +func NewInfrastructure( + furyctlConf schema.EksclusterKfdV1Alpha2, + kfdManifest config.KFD, + paths cluster.CreatorPaths, + dryRun bool, +) (*Infrastructure, error) { + infraDir := path.Join(paths.WorkDir, cluster.OperationPhaseInfrastructure) + + phase, err := cluster.NewOperationPhase(infraDir, kfdManifest.Tools, paths.BinPath) + if err != nil { + return nil, fmt.Errorf("error creating infrastructure phase: %w", err) + } + + executor := execx.NewStdExecutor() + + return &Infrastructure{ + OperationPhase: phase, + furyctlConf: furyctlConf, + kfdManifest: kfdManifest, + tfRunner: terraform.NewRunner( + executor, + terraform.Paths{ + Logs: phase.LogsPath, + Outputs: phase.OutputsPath, + WorkDir: path.Join(phase.Path, "terraform"), + Plan: phase.PlanPath, + Terraform: phase.TerraformPath, + }, + ), + faRunner: furyagent.NewRunner(executor, furyagent.Paths{ + Furyagent: path.Join(paths.BinPath, "furyagent", kfdManifest.Tools.Common.Furyagent.Version, "furyagent"), + WorkDir: phase.SecretsPath, + }), + ovRunner: openvpn.NewRunner(executor, openvpn.Paths{ + WorkDir: phase.SecretsPath, + Openvpn: "openvpn", + }), + dryRun: dryRun, + }, nil +} + +func (i *Infrastructure) Exec(opts []cluster.OperationPhaseOption) error { + logrus.Info("Running infrastructure phase...") + + timestamp := time.Now().Unix() + + if err := i.CreateFolder(); err != nil { + return fmt.Errorf("error creating infrastructure folder: %w", err) + } + + if err := i.copyFromTemplate(); err != nil { + return err + } + + if err := i.CreateFolderStructure(); err != nil { + return fmt.Errorf("error creating infrastructure folder structure: %w", err) + } + + if _, err := i.ovRunner.Version(); err != nil { + return fmt.Errorf("can't get tool version: %w", err) + } + + if err := i.createTfVars(); err != nil { + return err + } + + if err := i.tfRunner.Init(); err != nil { + return fmt.Errorf("error running terraform init: %w", err) + } + + if err := i.tfRunner.Plan(timestamp); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + if i.dryRun { + return nil + } + + logrus.Info("Creating cloud resources, this could take a while...") + + if _, err := i.tfRunner.Apply(timestamp); err != nil { + return fmt.Errorf("cannot create cloud resources: %w", err) + } + + if i.isVpnConfigured() { + clientName, err := i.generateClientName() + if err != nil { + return err + } + + if err := i.faRunner.ConfigOpenvpnClient(clientName); err != nil { + return fmt.Errorf("error configuring openvpn client: %w", err) + } + + for _, opt := range opts { + if strings.ToLower(opt.Name) == cluster.OperationPhaseOptionVPNAutoConnect { + autoConnect, ok := opt.Value.(bool) + if autoConnect && ok { + logrus.Info("Connecting to VPN, you will be asked for your SUDO password...") + + if err := i.ovRunner.Connect(clientName); err != nil { + return fmt.Errorf("error connecting to VPN: %w", err) + } + } + } + } + + if err := i.copyOpenvpnToWorkDir(); err != nil { + return fmt.Errorf("error copying openvpn file to workdir: %w", err) + } + } + + return nil +} + +func (i *Infrastructure) isVpnConfigured() bool { + vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn + if vpn == nil { + return false + } + + instances := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances + if instances == nil { + return true + } + + return *instances > 0 +} + +func (i *Infrastructure) generateClientName() (string, error) { + whoamiResp, err := exec.Command("whoami").Output() + if err != nil { + return "", fmt.Errorf("error getting current user: %w", err) + } + + whoami := strings.TrimSpace(string(whoamiResp)) + + return fmt.Sprintf("%s-%s", i.furyctlConf.Metadata.Name, whoami), nil +} + +func (i *Infrastructure) copyFromTemplate() error { + var cfg template.Config + + tmpFolder, err := os.MkdirTemp("", "furyctl-infra-configs-") + if err != nil { + return fmt.Errorf("error creating temp folder: %w", err) + } + + defer os.RemoveAll(tmpFolder) + + subFS, err := fs.Sub(configs.Tpl, path.Join("provisioners", "bootstrap", "aws")) + if err != nil { + return fmt.Errorf("error getting subfs: %w", err) + } + + if err = iox.CopyRecursive(subFS, tmpFolder); err != nil { + return fmt.Errorf("error copying template files: %w", err) + } + + targetTfDir := path.Join(i.Path, "terraform") + prefix := "infra" + + cfg.Data = map[string]map[any]any{ + "kubernetes": { + "eks": i.kfdManifest.Kubernetes.Eks, + }, + "terraform": { + "backend": map[string]any{ + "s3": map[string]any{ + "bucketName": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName, + "keyPrefix": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.KeyPrefix, + "region": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + }, + }, + }, + } + + err = i.OperationPhase.CopyFromTemplate( + cfg, + prefix, + tmpFolder, + targetTfDir, + ) + if err != nil { + return fmt.Errorf("error generating from template files: %w", err) + } + + return nil +} + +func (i *Infrastructure) copyOpenvpnToWorkDir() error { + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current dir: %w", err) + } + + ovpnFileName, err := i.generateClientName() + if err != nil { + return err + } + + ovpnFileName = fmt.Sprintf("%s.ovpn", ovpnFileName) + + ovpnPath, err := filepath.Abs(path.Join(i.SecretsPath, ovpnFileName)) + if err != nil { + return fmt.Errorf("error getting ovpn absolute path: %w", err) + } + + ovpnFile, err := os.ReadFile(ovpnPath) + if err != nil { + return fmt.Errorf("error reading ovpn file: %w", err) + } + + err = os.WriteFile(path.Join(currentDir, ovpnFileName), ovpnFile, iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing ovpn file: %w", err) + } + + return nil +} + +func (i *Infrastructure) createTfVars() error { + var buffer bytes.Buffer + + _, err := buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", i.furyctlConf.Metadata.Name)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf( + "network_cidr = \"%v\"\n", + i.furyctlConf.Spec.Infrastructure.Vpc.Network.Cidr, + )) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + publicSubnetworkCidrs := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpc.Network.SubnetsCidrs.Public)) + + for i, cidr := range i.furyctlConf.Spec.Infrastructure.Vpc.Network.SubnetsCidrs.Public { + publicSubnetworkCidrs[i] = fmt.Sprintf("\"%v\"", cidr) + } + + privateSubnetworkCidrs := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpc.Network.SubnetsCidrs.Private)) + + for i, cidr := range i.furyctlConf.Spec.Infrastructure.Vpc.Network.SubnetsCidrs.Private { + privateSubnetworkCidrs[i] = fmt.Sprintf("\"%v\"", cidr) + } + + _, err = buffer.WriteString(fmt.Sprintf( + "public_subnetwork_cidrs = [%v]\n", + strings.Join(publicSubnetworkCidrs, ","))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf( + "private_subnetwork_cidrs = [%v]\n", + strings.Join(privateSubnetworkCidrs, ","))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + err = i.addVpnDataToTfVars(&buffer) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + return i.writeTfVars(buffer) +} + +func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { + _, err := buffer.WriteString( + fmt.Sprintf( + "vpn_subnetwork_cidr = \"%v\"\n", + i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.VpnClientsSubnetCidr, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances != nil { + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_instances = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port != 0 { + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_port = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType != nil && + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType != "" { + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_instance_type = \"%v\"\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize != nil && + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize != 0 { + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_instance_disk_size = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName != nil && + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName != "" { + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_operator_name = \"%v\"\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits != nil && + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits != 0 { + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_dhparams_bits = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.AllowedFromCidrs) != 0 { + allowedCidrs := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.AllowedFromCidrs)) + + for i, cidr := range i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.AllowedFromCidrs { + allowedCidrs[i] = fmt.Sprintf("\"%v\"", cidr) + } + + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_operator_cidrs = [%v]\n", + strings.Join(allowedCidrs, ","), + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.GithubUsersName) != 0 { + githubUsers := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.GithubUsersName)) + + for i, gu := range i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.GithubUsersName { + githubUsers[i] = fmt.Sprintf("\"%v\"", gu) + } + + _, err = buffer.WriteString( + fmt.Sprintf( + "vpn_ssh_users = [%v]\n", + strings.Join(githubUsers, ","), + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + return nil +} + +func (i *Infrastructure) writeTfVars(buffer bytes.Buffer) error { + targetTfVars := path.Join(i.Path, "terraform", "main.auto.tfvars") + + err := os.WriteFile(targetTfVars, buffer.Bytes(), iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing terraform vars: %w", err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go new file mode 100644 index 000000000..6430a4830 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -0,0 +1,620 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package create + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/furyctl/configs" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/template" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + kubex "github.com/sighupio/furyctl/internal/x/kube" +) + +var ( + errKubeconfigFromLogs = errors.New("can't get kubeconfig from logs") + errPvtSubnetNotFound = errors.New("private_subnets not found in infra output") + errPvtSubnetFromOut = errors.New("cannot read private_subnets from infrastructure's output.json") + errVpcCIDRFromOut = errors.New("cannot read vpc_cidr_block from infrastructure's output.json") + errVpcCIDRNotFound = errors.New("vpc_cidr_block not found in infra output") + errVpcIDNotFound = errors.New("vpc id not found: you forgot to specify one or the infrastructure phase failed") +) + +const ( + nodePoolDefaultVolumeSize = 35 +) + +type Kubernetes struct { + *cluster.OperationPhase + furyctlConf schema.EksclusterKfdV1Alpha2 + kfdManifest config.KFD + infraOutputsPath string + tfRunner *terraform.Runner + dryRun bool +} + +func NewKubernetes( + furyctlConf schema.EksclusterKfdV1Alpha2, + kfdManifest config.KFD, + infraOutputsPath string, + paths cluster.CreatorPaths, + dryRun bool, +) (*Kubernetes, error) { + kubeDir := path.Join(paths.WorkDir, cluster.OperationPhaseKubernetes) + + phase, err := cluster.NewOperationPhase(kubeDir, kfdManifest.Tools, paths.BinPath) + if err != nil { + return nil, fmt.Errorf("error creating kubernetes phase: %w", err) + } + + return &Kubernetes{ + OperationPhase: phase, + furyctlConf: furyctlConf, + kfdManifest: kfdManifest, + infraOutputsPath: infraOutputsPath, + tfRunner: terraform.NewRunner( + execx.NewStdExecutor(), + terraform.Paths{ + Logs: phase.LogsPath, + Outputs: phase.OutputsPath, + WorkDir: path.Join(phase.Path, "terraform"), + Plan: phase.PlanPath, + Terraform: phase.TerraformPath, + }, + ), + dryRun: dryRun, + }, nil +} + +func (k *Kubernetes) Exec() error { + logrus.Info("Running kubernetes phase...") + + timestamp := time.Now().Unix() + + if err := k.CreateFolder(); err != nil { + return fmt.Errorf("error creating kubernetes phase folder: %w", err) + } + + if err := k.copyFromTemplate(); err != nil { + return err + } + + if err := k.CreateFolderStructure(); err != nil { + return fmt.Errorf("error creating kubernetes phase folder structure: %w", err) + } + + if err := k.createTfVars(); err != nil { + return err + } + + if err := k.tfRunner.Init(); err != nil { + return fmt.Errorf("error running terraform init: %w", err) + } + + if err := k.tfRunner.Plan(timestamp); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + if k.dryRun { + return nil + } + + logrus.Info("Creating cloud resources, this could take a while...") + + out, err := k.tfRunner.Apply(timestamp) + if err != nil { + return fmt.Errorf("cannot create cloud resources: %w", err) + } + + if out.Outputs["kubeconfig"] == nil { + return errKubeconfigFromLogs + } + + kubeString, ok := out.Outputs["kubeconfig"].Value.(string) + if !ok { + return errKubeconfigFromLogs + } + + p, err := kubex.CreateConfig([]byte(kubeString), k.SecretsPath) + if err != nil { + return fmt.Errorf("error creating kubeconfig: %w", err) + } + + if err := kubex.SetConfigEnv(p); err != nil { + return fmt.Errorf("error setting kubeconfig env: %w", err) + } + + if err := kubex.CopyConfigToWorkDir(p); err != nil { + return fmt.Errorf("error copying kubeconfig: %w", err) + } + + return nil +} + +func (k *Kubernetes) copyFromTemplate() error { + var cfg template.Config + + tmpFolder, err := os.MkdirTemp("", "furyctl-kube-configs-") + if err != nil { + return fmt.Errorf("error creating temp folder: %w", err) + } + + defer os.RemoveAll(tmpFolder) + + subFS, err := fs.Sub(configs.Tpl, path.Join("provisioners", "cluster", "eks")) + if err != nil { + return fmt.Errorf("error getting subfs: %w", err) + } + + err = iox.CopyRecursive(subFS, tmpFolder) + if err != nil { + return fmt.Errorf("error copying template files: %w", err) + } + + targetTfDir := path.Join(k.Path, "terraform") + prefix := "kube" + tfConfVars := map[string]map[any]any{ + "kubernetes": { + "eks": k.kfdManifest.Kubernetes.Eks, + }, + "terraform": { + "backend": map[string]any{ + "s3": map[string]any{ + "bucketName": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName, + "keyPrefix": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.KeyPrefix, + "region": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + }, + }, + }, + } + + cfg.Data = tfConfVars + + err = k.OperationPhase.CopyFromTemplate( + cfg, + prefix, + tmpFolder, + targetTfDir, + ) + if err != nil { + return fmt.Errorf("error generating from template files: %w", err) + } + + return nil +} + +//nolint:gocyclo,maintidx,gocognit,funlen,revive,cyclop // it will be refactored +func (k *Kubernetes) createTfVars() error { + var buffer bytes.Buffer + + var allowedCidrsSource []schema.TypesCidr + + subnetIdsSource := k.furyctlConf.Spec.Kubernetes.SubnetIds + vpcIDSource := k.furyctlConf.Spec.Kubernetes.VpcId + + if k.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess != nil { + allowedCidrsSource = k.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.AllowedCidrs + } + + if infraOutJSON, err := os.ReadFile(path.Join(k.infraOutputsPath, "output.json")); err == nil { + var infraOut terraform.OutputJSON + + if err := json.Unmarshal(infraOutJSON, &infraOut); err == nil { + if infraOut.Outputs["private_subnets"] == nil { + return errPvtSubnetNotFound + } + + s, ok := infraOut.Outputs["private_subnets"].Value.([]interface{}) + if !ok { + return errPvtSubnetFromOut + } + + if infraOut.Outputs["vpc_id"] == nil { + return ErrVpcIDNotFound + } + + v, ok := infraOut.Outputs["vpc_id"].Value.(string) + if !ok { + return ErrVpcIDFromOut + } + + if infraOut.Outputs["vpc_cidr_block"] == nil { + return errVpcCIDRNotFound + } + + c, ok := infraOut.Outputs["vpc_cidr_block"].Value.(string) + if !ok { + return errVpcCIDRFromOut + } + + subs := make([]schema.TypesAwsSubnetId, len(s)) + + for i, sub := range s { + ss, ok := sub.(string) + if !ok { + return errPvtSubnetFromOut + } + + subs[i] = schema.TypesAwsSubnetId(ss) + } + + subnetIdsSource = subs + vpcID := schema.TypesAwsVpcId(v) + vpcIDSource = &vpcID + allowedCidrsSource = []schema.TypesCidr{schema.TypesCidr(c)} + } + } + + _, err := buffer.WriteString(fmt.Sprintf("cluster_name = \"%v\"\n", k.furyctlConf.Metadata.Name)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("cluster_version = \"%v\"\n", k.kfdManifest.Kubernetes.Eks.Version)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if vpcIDSource == nil { + if !k.dryRun { + return errVpcIDNotFound + } + + vpcIDSource = new(schema.TypesAwsVpcId) + } + + _, err = buffer.WriteString(fmt.Sprintf("network = \"%v\"\n", *vpcIDSource)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + subnetIds := make([]string, len(subnetIdsSource)) + + for i, subnetID := range subnetIdsSource { + subnetIds[i] = fmt.Sprintf("\"%v\"", subnetID) + } + + _, err = buffer.WriteString(fmt.Sprintf("subnetworks = [%v]\n", strings.Join(subnetIds, ","))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + dmzCidrRange := make([]string, len(allowedCidrsSource)) + + for i, cidr := range allowedCidrsSource { + dmzCidrRange[i] = fmt.Sprintf("\"%v\"", cidr) + } + + _, err = buffer.WriteString(fmt.Sprintf("dmz_cidr_range = [%v]\n", strings.Join(dmzCidrRange, ","))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString( + fmt.Sprintf("ssh_public_key = \"%v\"\n", k.furyctlConf.Spec.Kubernetes.NodeAllowedSshPublicKey), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if k.furyctlConf.Spec.Tags != nil && len(k.furyctlConf.Spec.Tags) > 0 { + var tags []byte + + tags, err := json.Marshal(k.furyctlConf.Spec.Tags) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if k.furyctlConf.Spec.Kubernetes.AwsAuth != nil { + if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts) > 0 { + _, err = buffer.WriteString( + fmt.Sprintf( + "eks_map_accounts = [\"%v\"]\n", + strings.Join(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts, "\",\""), + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users) > 0 { + _, err = buffer.WriteString("eks_map_users = [\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for _, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Users { + _, err = buffer.WriteString( + fmt.Sprintf( + `{ + groups = ["%v"] + username = "%v" + userarn = "%v" + },`, + strings.Join(account.Groups, "\",\""), account.Username, account.Userarn, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + _, err = buffer.WriteString("]\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles) > 0 { + _, err = buffer.WriteString("eks_map_roles = [\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for _, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles { + _, err = buffer.WriteString( + fmt.Sprintf( + `{ + groups = ["%v"] + username = "%v" + rolearn = "%v" + },`, + strings.Join(account.Groups, "\",\""), account.Username, account.Rolearn, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + _, err = buffer.WriteString("]\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + } + + if len(k.furyctlConf.Spec.Kubernetes.NodePools) > 0 { + _, err = buffer.WriteString("node_pools = [\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for _, np := range k.furyctlConf.Spec.Kubernetes.NodePools { + _, err = buffer.WriteString("{\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", np.Name)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString("version = null\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + spot := "false" + + if np.Instance.Spot != nil { + spot = strconv.FormatBool(*np.Instance.Spot) + } + + _, err = buffer.WriteString(fmt.Sprintf("spot_instance = %v\n", spot)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("min_size = %v\n", np.Size.Min)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("max_size = %v\n", np.Size.Max)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("instance_type = \"%v\"\n", np.Instance.Type)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if len(np.AttachedTargetGroups) > 0 { + attachedTargetGroups := make([]string, len(np.AttachedTargetGroups)) + + for i, tg := range np.AttachedTargetGroups { + attachedTargetGroups[i] = fmt.Sprintf("\"%v\"", tg) + } + + _, err = buffer.WriteString( + fmt.Sprintf( + "eks_target_group_arns = [%v]\n", + strings.Join(attachedTargetGroups, ","), + )) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + volumeSize := nodePoolDefaultVolumeSize + + if np.Instance.VolumeSize != nil { + volumeSize = *np.Instance.VolumeSize + } + + _, err = buffer.WriteString(fmt.Sprintf("volume_size = %v\n", volumeSize)) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if len(np.AdditionalFirewallRules) > 0 { + _, err = buffer.WriteString("additional_firewall_rules = [\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for _, fwRule := range np.AdditionalFirewallRules { + fwRuleTags := "{}" + + if len(fwRule.Tags) > 0 { + var tags []byte + + tags, err := json.Marshal(fwRule.Tags) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + fwRuleTags = string(tags) + } + + _, err = buffer.WriteString( + fmt.Sprintf( + `{ + name = "%v" + direction = "%v" + cidr_block = "%v" + protocol = "%v" + ports = "%v" + tags = %v + },`, + fwRule.Name, + fwRule.Type, + fwRule.CidrBlocks, + fwRule.Protocol, + fwRule.Ports, + fwRuleTags, + ), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + _, err = buffer.WriteString("]\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + _, err = buffer.WriteString("additional_firewall_rules = []\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(np.SubnetIds) > 0 { + npSubNetIds := make([]string, len(np.SubnetIds)) + + for i, subnetID := range np.SubnetIds { + npSubNetIds[i] = fmt.Sprintf("\"%v\"", subnetID) + } + + _, err = buffer.WriteString(fmt.Sprintf("subnetworks = [%v]\n", strings.Join(npSubNetIds, ","))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + _, err = buffer.WriteString("subnetworks = null\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(np.Labels) > 0 { + var labels []byte + + labels, err := json.Marshal(np.Labels) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("labels = %v\n", string(labels))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + _, err = buffer.WriteString("labels = {}\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(np.Taints) > 0 { + _, err = buffer.WriteString(fmt.Sprintf("taints = [\"%v\"]\n", strings.Join(np.Taints, "\",\""))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + _, err = buffer.WriteString("taints = []\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(np.Tags) > 0 { + var tags []byte + + tags, err := json.Marshal(np.Tags) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + _, err = buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + _, err = buffer.WriteString("tags = {}\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + _, err = buffer.WriteString("},\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + _, err = buffer.WriteString("]\n") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + targetTfVars := path.Join(k.Path, "terraform", "main.auto.tfvars") + + err = os.WriteFile(targetTfVars, buffer.Bytes(), iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing terraform vars file: %w", err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go new file mode 100644 index 000000000..2ec8762e6 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -0,0 +1,232 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +import ( + "errors" + "fmt" + "os" + "path" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/create" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/tool/kubectl" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + kubex "github.com/sighupio/furyctl/internal/x/kube" +) + +var ErrUnsupportedPhase = errors.New("unsupported phase") + +type ClusterCreator struct { + paths cluster.CreatorPaths + furyctlConf schema.EksclusterKfdV1Alpha2 + kfdManifest config.KFD + phase string + vpnAutoConnect bool + dryRun bool +} + +func (v *ClusterCreator) SetProperties(props []cluster.CreatorProperty) { + for _, prop := range props { + v.SetProperty(prop.Name, prop.Value) + } +} + +func (v *ClusterCreator) SetProperty(name string, value any) { + lcName := strings.ToLower(name) + + switch lcName { + case cluster.CreatorPropertyConfigPath: + if s, ok := value.(string); ok { + v.paths.ConfigPath = s + } + + case cluster.CreatorPropertyFuryctlConf: + if s, ok := value.(schema.EksclusterKfdV1Alpha2); ok { + v.furyctlConf = s + } + + case cluster.CreatorPropertyKfdManifest: + if s, ok := value.(config.KFD); ok { + v.kfdManifest = s + } + + case cluster.CreatorPropertyPhase: + if s, ok := value.(string); ok { + v.phase = s + } + + case cluster.CreatorPropertyVpnAutoConnect: + if b, ok := value.(bool); ok { + v.vpnAutoConnect = b + } + + case cluster.CreatorPropertyDistroPath: + if s, ok := value.(string); ok { + v.paths.DistroPath = s + } + + case cluster.CreatorPropertyWorkDir: + if s, ok := value.(string); ok { + v.paths.WorkDir = s + } + + case cluster.CreatorPropertyBinPath: + if s, ok := value.(string); ok { + v.paths.BinPath = s + } + + case cluster.CreatorPropertyKubeconfig: + if s, ok := value.(string); ok { + v.paths.Kubeconfig = s + } + + case cluster.CreatorPropertyDryRun: + if b, ok := value.(bool); ok { + v.dryRun = b + } + } +} + +func (v *ClusterCreator) Create(skipPhase string) error { + infra, err := create.NewInfrastructure(v.furyctlConf, v.kfdManifest, v.paths, v.dryRun) + if err != nil { + return fmt.Errorf("error while initiating infrastructure phase: %w", err) + } + + kube, err := create.NewKubernetes( + v.furyctlConf, + v.kfdManifest, + infra.OutputsPath, + v.paths, + v.dryRun, + ) + if err != nil { + return fmt.Errorf("error while initiating kubernetes phase: %w", err) + } + + distro, err := create.NewDistribution( + v.paths, + v.furyctlConf, + v.kfdManifest, + infra.OutputsPath, + v.dryRun, + ) + if err != nil { + return fmt.Errorf("error while initiating distribution phase: %w", err) + } + + infraOpts := []cluster.OperationPhaseOption{ + {Name: cluster.OperationPhaseOptionVPNAutoConnect, Value: v.vpnAutoConnect}, + } + + switch v.phase { + case cluster.OperationPhaseInfrastructure: + if err = infra.Exec(infraOpts); err != nil { + return fmt.Errorf("error while executing infrastructure phase: %w", err) + } + + return nil + + case cluster.OperationPhaseKubernetes: + if err = kube.Exec(); err != nil { + return fmt.Errorf("error while executing kubernetes phase: %w", err) + } + + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + } + + return nil + + case cluster.OperationPhaseDistribution: + if err = distro.Exec(); err != nil { + return fmt.Errorf("error while executing distribution phase: %w", err) + } + + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + } + + return nil + + case cluster.OperationPhaseAll: + if v.furyctlConf.Spec.Infrastructure != nil && + (skipPhase == "" || skipPhase == cluster.OperationPhaseDistribution) { + if err := infra.Exec(infraOpts); err != nil { + return fmt.Errorf("error while executing infrastructure phase: %w", err) + } + } + + if skipPhase != cluster.OperationPhaseKubernetes { + if err := kube.Exec(); err != nil { + return fmt.Errorf("error while executing kubernetes phase: %w", err) + } + + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + } + + if skipPhase != cluster.OperationPhaseDistribution { + if err = distro.Exec(); err != nil { + return fmt.Errorf("error while executing distribution phase: %w", err) + } + + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + } + + return nil + + default: + return ErrUnsupportedPhase + } +} + +func (v *ClusterCreator) storeClusterConfig() error { + x, err := os.ReadFile(v.paths.ConfigPath) + if err != nil { + return fmt.Errorf("error while reading config file: %w", err) + } + + secret, err := kubex.CreateSecret(x, "furyctl-config", "kube-system") + if err != nil { + return fmt.Errorf("error while creating secret: %w", err) + } + + secretPath := path.Join(v.paths.WorkDir, "secrets.yaml") + + if err := iox.WriteFile(secretPath, secret); err != nil { + return fmt.Errorf("error while writing secret: %w", err) + } + + defer os.Remove(secretPath) + + runner := kubectl.NewRunner(execx.NewStdExecutor(), kubectl.Paths{ + Kubectl: path.Join(v.paths.BinPath, "kubectl", v.kfdManifest.Tools.Common.Kubectl.Version, "kubectl"), + WorkDir: v.paths.WorkDir, + Kubeconfig: v.paths.Kubeconfig, + }, true, true) + + logrus.Info("Storing cluster config...") + + if err := runner.Apply(secretPath); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go new file mode 100644 index 000000000..fb71c3a1f --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -0,0 +1,392 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package del + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/tool/kubectl" + "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +const ( + ingressAfterDeleteDelay = 4 + checkPendingResourcesDelay = 20 + checkPendingResourcesMaxRetries = 5 +) + +var ( + errCheckPendingResources = errors.New("error while checking pending resources") + errPendingResources = errors.New("pending resources: ") +) + +type Distribution struct { + *cluster.OperationPhase + tfRunner *terraform.Runner + kzRunner *kustomize.Runner + kubeRunner *kubectl.Runner + dryRun bool +} + +func NewDistribution( + dryRun bool, + workDir, + binPath string, + kfdManifest config.KFD, + kubeconfig string, +) (*Distribution, error) { + distroDir := path.Join(workDir, cluster.OperationPhaseDistribution) + + phase, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, binPath) + if err != nil { + return nil, fmt.Errorf("error creating distribution phase: %w", err) + } + + return &Distribution{ + OperationPhase: phase, + tfRunner: terraform.NewRunner( + execx.NewStdExecutor(), + terraform.Paths{ + Logs: phase.LogsPath, + Outputs: phase.OutputsPath, + WorkDir: path.Join(phase.Path, "terraform"), + Plan: phase.PlanPath, + Terraform: phase.TerraformPath, + }, + ), + kzRunner: kustomize.NewRunner( + execx.NewStdExecutor(), + kustomize.Paths{ + Kustomize: phase.KustomizePath, + WorkDir: path.Join(phase.Path, "manifests"), + }, + ), + kubeRunner: kubectl.NewRunner( + execx.NewStdExecutor(), + kubectl.Paths{ + Kubectl: phase.KubectlPath, + WorkDir: path.Join(phase.Path, "manifests"), + Kubeconfig: kubeconfig, + }, + true, + true, + ), + dryRun: dryRun, + }, nil +} + +func (d *Distribution) Exec() error { + logrus.Info("Deleting distribution phase...") + + err := iox.CheckDirIsEmpty(d.OperationPhase.Path) + if err == nil { + logrus.Infof("distribution phase already executed, skipping") + + return nil + } + + if d.dryRun { + timestamp := time.Now().Unix() + + if err := d.tfRunner.Init(); err != nil { + return fmt.Errorf("error running terraform init: %w", err) + } + + if err := d.tfRunner.Plan(timestamp, "-destroy"); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + manifestsOutPath, err := d.buildManifests() + if err != nil { + return err + } + + err = d.kubeRunner.Delete(manifestsOutPath, "--dry-run=client") + if err != nil { + logrus.Errorf("error while deleting resources: %v", err) + } + + logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") + + err = d.getListOfResourcesNs("all", "ingress") + if err != nil { + logrus.Errorf("error while getting list of ingress resources: %v", err) + } + + err = d.getListOfResourcesNs("monitoring", "prometheus") + if err != nil { + logrus.Errorf("error while getting list of prometheus resources: %v", err) + } + + err = d.getListOfResourcesNs("monitoring", "persistentvolumeclaim") + if err != nil { + logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) + } + + err = d.getListOfResourcesNs("logging", "persistentvolumeclaim") + if err != nil { + logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) + } + + err = d.getListOfResourcesNs("logging", "statefulset") + if err != nil { + logrus.Errorf("error while getting list of statefulset resources: %v", err) + } + + err = d.getListOfResourcesNs("logging", "logging") + if err != nil { + logrus.Errorf("error while getting list of logging resources: %v", err) + } + + err = d.getListOfResourcesNs("ingress-nginx", "service") + if err != nil { + logrus.Errorf("error while getting list of service resources: %v", err) + } + + return nil + } + + logrus.Info("Deleting ingresses...") + + if err = d.deleteIngresses(); err != nil { + return err + } + + logrus.Info("Deleting blocking resources...") + + if err = d.deleteBlockingResources(); err != nil { + return err + } + + logrus.Info("Building manifests...") + + manifestsOutPath, err := d.buildManifests() + if err != nil { + return err + } + + logrus.Info("Deleting manifests...") + + err = d.kubeRunner.Delete(manifestsOutPath) + if err != nil { + logrus.Errorf("error while deleting resources: %v", err) + } + + logrus.Info("Checking pending resources...") + + err = d.checkPendingResources() + if err != nil { + return err + } + + logrus.Info("Deleting infra resources...") + + err = d.tfRunner.Destroy() + if err != nil { + return fmt.Errorf("error while deleting infra resources: %w", err) + } + + return nil +} + +func (d *Distribution) buildManifests() (string, error) { + kzOut, err := d.kzRunner.Build() + if err != nil { + return "", fmt.Errorf("error building manifests: %w", err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") + if err != nil { + return "", fmt.Errorf("error creating temp dir: %w", err) + } + + manifestsOutPath := filepath.Join(outDirPath, "out.yaml") + + logrus.Debugf("built manifests = %s", manifestsOutPath) + + if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { + return "", fmt.Errorf("error writing built manifests: %w", err) + } + + return manifestsOutPath, nil +} + +func (d *Distribution) checkPendingResources() error { + var errSvc, errPv, errIgrs error + + dur := time.Second * checkPendingResourcesDelay + + maxRetries := checkPendingResourcesMaxRetries + + retries := 0 + + for retries < maxRetries { + p := time.NewTicker(dur) + + if <-p.C; true { + errSvc = d.getLoadBalancers() + + errPv = d.getPersistentVolumes() + + errIgrs = d.getIngresses() + + if errSvc == nil && errPv == nil && errIgrs == nil { + return nil + } + } + + retries++ + + p.Stop() + } + + return fmt.Errorf("%w:\n%v\n%v\n%v", errCheckPendingResources, errSvc, errPv, errIgrs) +} + +func (d *Distribution) deleteIngresses() error { + dur := time.Minute * ingressAfterDeleteDelay + + _, err := d.kubeRunner.DeleteAllResources("ingress", "all") + if err != nil { + return fmt.Errorf("error deleting ingresses: %w", err) + } + + logrus.Debugf("waiting for records to be deleted...") + time.Sleep(dur) + + return nil +} + +func (d *Distribution) deleteBlockingResources() error { + dur := time.Minute * ingressAfterDeleteDelay + + _, err := d.kubeRunner.DeleteAllResources("prometheus", "monitoring") + if err != nil { + return fmt.Errorf("error deleting prometheus resources: %w", err) + } + + _, err = d.kubeRunner.DeleteAllResources("pvc", "monitoring") + if err != nil { + return fmt.Errorf("error deleting pvc in namespace 'monitoring': %w", err) + } + + _, err = d.kubeRunner.DeleteAllResources("logging", "logging") + if err != nil { + return fmt.Errorf("error deleting logging resources: %w", err) + } + + _, err = d.kubeRunner.DeleteAllResources("sts", "logging") + if err != nil { + return fmt.Errorf("error deleting sts in namespace 'logging': %w", err) + } + + _, err = d.kubeRunner.DeleteAllResources("pvc", "logging") + if err != nil { + return fmt.Errorf("error deleting pvc in namespace 'logging': %w", err) + } + + _, err = d.kubeRunner.DeleteAllResources("svc", "ingress-nginx") + if err != nil { + return fmt.Errorf("error deleting svc in namespace 'ingress-nginx': %w", err) + } + + logrus.Debugf("waiting for resources to be deleted...") + time.Sleep(dur) + + return nil +} + +func (d *Distribution) getLoadBalancers() error { + log, err := d.kubeRunner.Get("all", "svc", "-o", + "jsonpath='{.items[?(@.spec.type==\"LoadBalancer\")].metadata.name}'") + if err != nil { + return fmt.Errorf("error while reading resources from cluster: %w", err) + } + + reg := regexp.MustCompile(`'(.*?)'`) + + logStringIndex := reg.FindStringIndex(log) + + if len(logStringIndex) == 0 { + return fmt.Errorf("%w: error while parsing kubectl get response", errPendingResources) + } + + logString := log[logStringIndex[0]:logStringIndex[1]] + + if logString != "''" { + return fmt.Errorf("%w: %s", errPendingResources, logString) + } + + return nil +} + +func (d *Distribution) getListOfResourcesNs(ns, resName string) error { + _, err := d.kubeRunner.Get(ns, resName, "-o", + "jsonpath={range .items[*]}{\""+resName+" \"}\"{.metadata.name}\"{\" deleted (dry run)\"}{\"\\n\"}{end}") + if err != nil { + return fmt.Errorf("error while reading resources from cluster: %w", err) + } + + return nil +} + +func (d *Distribution) getIngresses() error { + log, err := d.kubeRunner.Get("all", "ingress", "-o", "jsonpath='{.items[*].metadata.name}'") + if err != nil { + return fmt.Errorf("error while reading resources from cluster: %w", err) + } + + reg := regexp.MustCompile(`'(.*?)'`) + + logStringIndex := reg.FindStringIndex(log) + + if len(logStringIndex) == 0 { + return fmt.Errorf("%w: error while parsing kubectl get response", errPendingResources) + } + + logString := log[logStringIndex[0]:logStringIndex[1]] + + if logString != "''" { + return fmt.Errorf("%w: %s", errPendingResources, logString) + } + + return nil +} + +func (d *Distribution) getPersistentVolumes() error { + log, err := d.kubeRunner.Get("all", "pv", "-o", "jsonpath='{.items[*].metadata.name}'") + if err != nil { + return fmt.Errorf("error while reading resources from cluster: %w", err) + } + + reg := regexp.MustCompile(`'(.*?)'`) + + logStringIndex := reg.FindStringIndex(log) + + if len(logStringIndex) == 0 { + return fmt.Errorf("%w: error while parsing kubectl get response", errPendingResources) + } + + logString := log[logStringIndex[0]:logStringIndex[1]] + + if logString != "''" { + return fmt.Errorf("%w: %s", errPendingResources, logString) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go new file mode 100644 index 000000000..eacf7a753 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -0,0 +1,80 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package del + +import ( + "fmt" + "path" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +type Infrastructure struct { + *cluster.OperationPhase + tfRunner *terraform.Runner + dryRun bool +} + +func NewInfrastructure(dryRun bool, workDir, binPath string, kfdManifest config.KFD) (*Infrastructure, error) { + infraDir := path.Join(workDir, cluster.OperationPhaseInfrastructure) + + phase, err := cluster.NewOperationPhase(infraDir, kfdManifest.Tools, binPath) + if err != nil { + return nil, fmt.Errorf("error creating infrastructure phase: %w", err) + } + + return &Infrastructure{ + OperationPhase: phase, + tfRunner: terraform.NewRunner( + execx.NewStdExecutor(), + terraform.Paths{ + Logs: phase.LogsPath, + Outputs: phase.OutputsPath, + WorkDir: path.Join(phase.Path, "terraform"), + Plan: phase.PlanPath, + Terraform: phase.TerraformPath, + }, + ), + dryRun: dryRun, + }, nil +} + +func (i *Infrastructure) Exec() error { + logrus.Info("Deleting infrastructure phase...") + + timestamp := time.Now().Unix() + + err := iox.CheckDirIsEmpty(i.OperationPhase.Path) + if err == nil { + logrus.Infof("infrastructure phase already executed, skipping...") + + return nil + } + + if err := i.tfRunner.Plan(timestamp, "-destroy"); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + if i.dryRun { + return nil + } + + err = i.tfRunner.Destroy() + if err != nil { + return fmt.Errorf("error while deleting infrastructure: %w", err) + } + + logrus.Warnf("Please, remember to kill the OpenVPN process if" + + " you have chosen to create it in the infrastructure phase") + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go new file mode 100644 index 000000000..972104742 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -0,0 +1,81 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package del + +import ( + "fmt" + "path" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +type Kubernetes struct { + *cluster.OperationPhase + tfRunner *terraform.Runner + dryRun bool +} + +func NewKubernetes(dryRun bool, workDir, binPath string, kfdManifest config.KFD) (*Kubernetes, error) { + kubeDir := path.Join(workDir, cluster.OperationPhaseKubernetes) + + phase, err := cluster.NewOperationPhase(kubeDir, kfdManifest.Tools, binPath) + if err != nil { + return nil, fmt.Errorf("error creating kubernetes phase: %w", err) + } + + return &Kubernetes{ + OperationPhase: phase, + tfRunner: terraform.NewRunner( + execx.NewStdExecutor(), + terraform.Paths{ + Logs: phase.LogsPath, + Outputs: phase.OutputsPath, + WorkDir: path.Join(phase.Path, "terraform"), + Plan: phase.PlanPath, + Terraform: phase.TerraformPath, + }, + ), + dryRun: dryRun, + }, nil +} + +func (k *Kubernetes) Exec() error { + logrus.Info("Deleting kubernetes phase...") + + timestamp := time.Now().Unix() + + err := iox.CheckDirIsEmpty(k.OperationPhase.Path) + if err == nil { + logrus.Infof("kubernetes phase already executed, skipping...") + + return nil + } + + if err := k.tfRunner.Init(); err != nil { + return fmt.Errorf("error running terraform init: %w", err) + } + + if err := k.tfRunner.Plan(timestamp, "-destroy"); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + if k.dryRun { + return nil + } + + err = k.tfRunner.Destroy() + if err != nil { + return fmt.Errorf("error while deleting kubernetes phase: %w", err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go new file mode 100644 index 000000000..0fca39e4f --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -0,0 +1,117 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +import ( + "fmt" + "strings" + + "github.com/sighupio/fury-distribution/pkg/config" + del "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/delete" + "github.com/sighupio/furyctl/internal/cluster" +) + +type ClusterDeleter struct { + kfdManifest config.KFD + phase string + workDir string + binPath string + kubeconfig string +} + +func (d *ClusterDeleter) SetProperties(props []cluster.DeleterProperty) { + for _, prop := range props { + d.SetProperty(prop.Name, prop.Value) + } +} + +func (d *ClusterDeleter) SetProperty(name string, value any) { + lcName := strings.ToLower(name) + + switch lcName { + case cluster.DeleterPropertyKfdManifest: + if kfdManifest, ok := value.(config.KFD); ok { + d.kfdManifest = kfdManifest + } + + case cluster.DeleterPropertyPhase: + if s, ok := value.(string); ok { + d.phase = s + } + + case cluster.DeleterPropertyWorkDir: + if s, ok := value.(string); ok { + d.workDir = s + } + + case cluster.DeleterPropertyBinPath: + if s, ok := value.(string); ok { + d.binPath = s + } + + case cluster.DeleterPropertyKubeconfig: + if s, ok := value.(string); ok { + d.kubeconfig = s + } + } +} + +func (d *ClusterDeleter) Delete(dryRun bool) error { + distro, err := del.NewDistribution(dryRun, d.workDir, d.binPath, d.kfdManifest, d.kubeconfig) + if err != nil { + return fmt.Errorf("error while creating distribution phase: %w", err) + } + + kube, err := del.NewKubernetes(dryRun, d.workDir, d.binPath, d.kfdManifest) + if err != nil { + return fmt.Errorf("error while creating kubernetes phase: %w", err) + } + + infra, err := del.NewInfrastructure(dryRun, d.workDir, d.binPath, d.kfdManifest) + if err != nil { + return fmt.Errorf("error while creating infrastructure phase: %w", err) + } + + switch d.phase { + case cluster.OperationPhaseInfrastructure: + if err := infra.Exec(); err != nil { + return fmt.Errorf("error while deleting infrastructure phase: %w", err) + } + + return nil + + case cluster.OperationPhaseKubernetes: + if err := kube.Exec(); err != nil { + return fmt.Errorf("error while deleting kubernetes phase: %w", err) + } + + return nil + + case cluster.OperationPhaseDistribution: + if err := distro.Exec(); err != nil { + return fmt.Errorf("error while deleting distribution phase: %w", err) + } + + return nil + + case cluster.OperationPhaseAll: + if err := distro.Exec(); err != nil { + return fmt.Errorf("error while deleting distribution phase: %w", err) + } + + if err := kube.Exec(); err != nil { + return fmt.Errorf("error while deleting kubernetes phase: %w", err) + } + + if err := infra.Exec(); err != nil { + return fmt.Errorf("error while deleting infrastructure phase: %w", err) + } + + return nil + + default: + return ErrUnsupportedPhase + } +} diff --git a/internal/apis/kfd/v1alpha2/eks/init.go b/internal/apis/kfd/v1alpha2/eks/init.go new file mode 100644 index 000000000..e1654690e --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/init.go @@ -0,0 +1,25 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +import ( + "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/furyctl/internal/cluster" +) + +//nolint:gochecknoinits // this pattern requires init function to work. +func init() { + cluster.RegisterCreatorFactory( + "kfd.sighup.io/v1alpha2", + "EKSCluster", + cluster.NewCreatorFactory[*ClusterCreator, schema.EksclusterKfdV1Alpha2](&ClusterCreator{}), + ) + + cluster.RegisterDeleterFactory( + "kfd.sighup.io/v1alpha2", + "EKSCluster", + cluster.NewDeleterFactory[*ClusterDeleter](&ClusterDeleter{}), + ) +} diff --git a/internal/app/common_test.go b/internal/app/common_test.go deleted file mode 100644 index 2ae015a29..000000000 --- a/internal/app/common_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package app_test - -import ( - "encoding/json" - "os" - "path/filepath" - "testing" - - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/yaml" -) - -var ( - furyConfig = map[string]interface{}{ - "apiVersion": "kfd.sighup.io/v1alpha2", - "kind": "EKSCluster", - "spec": map[string]interface{}{ - "distributionVersion": "v1.24.7", - "distribution": map[string]interface{}{}, - }, - } - - correctFuryctlDefaults = map[string]any{ - "data": map[string]any{ - "modules": map[string]any{ - "ingress": map[string]any{ - "test": "test", - }, - }, - }, - } - - wrongFuryctlDefaults = map[string]any{ - "data": map[string]any{ - "modules": map[string]any{ - "ingress": map[string]any{ - "test": "test", - "unexpected": "test", - }, - }, - }, - } - - correctKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: distribution.ManifestModules{}, - Kubernetes: distribution.ManifestKubernetes{}, - FuryctlSchemas: distribution.ManifestSchemas{}, - Tools: distribution.ManifestTools{ - Ansible: "2.11.2", - Furyagent: "0.3.0", - Kubectl: "1.21.1", - Kustomize: "3.9.4", - Terraform: "0.15.4", - }, - } - - wrongKFDConf = distribution.Manifest{ - Version: "v1.24.7", - Modules: distribution.ManifestModules{}, - Kubernetes: distribution.ManifestKubernetes{}, - FuryctlSchemas: distribution.ManifestSchemas{}, - Tools: distribution.ManifestTools{ - Ansible: "2.10.0", - Furyagent: "0.0.2", - Kubectl: "1.21.4", - Kustomize: "3.9.8", - Terraform: "0.15.9", - }, - } -) - -func mkDirTemp(t *testing.T, prefix string) string { - t.Helper() - - tmpDir, err := os.MkdirTemp("", prefix) - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - return tmpDir -} - -func rmDirTemp(t *testing.T, dir string) { - t.Helper() - - if err := os.RemoveAll(dir); err != nil { - t.Log(err) - } -} - -func setupDistroFolder(t *testing.T, furyctlDefaults map[string]any, kfdConf distribution.Manifest) (string, string) { - t.Helper() - - tmpDir := mkDirTemp(t, "furyctl-validate-test-") - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - kfdFilePath := filepath.Join(tmpDir, "kfd.yaml") - - kfdYaml, err := yaml.MarshalV2(kfdConf) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err = os.WriteFile(kfdFilePath, kfdYaml, os.ModePerm); err != nil { - t.Fatalf("error writing kfd file: %v", err) - } - - defaultsFilePath := filepath.Join(tmpDir, "furyctl-defaults.yaml") - - defaultsYaml, err := yaml.MarshalV2(furyctlDefaults) - if err != nil { - t.Fatalf("error marshaling furyctl defaults: %v", err) - } - - if err = os.WriteFile(defaultsFilePath, defaultsYaml, os.ModePerm); err != nil { - t.Fatalf("error writing furyctl defaults: %v", err) - } - - schemasPath := filepath.Join(tmpDir, "schemas") - - err = os.Mkdir(schemasPath, os.ModePerm) - if err != nil { - t.Fatalf("error creating schemas dir: %v", err) - } - - schemaFilePath := filepath.Join(schemasPath, "ekscluster-kfd-v1alpha2.json") - - schemaJson, err := json.Marshal(eksClusterJsonSchema) - if err != nil { - t.Fatalf("error marshaling schema: %v", err) - } - - if err = os.WriteFile(schemaFilePath, schemaJson, os.ModePerm); err != nil { - t.Fatalf("error writing schema json: %v", err) - } - - return tmpDir, configFilePath -} diff --git a/internal/app/download_dependencies.go b/internal/app/download_dependencies.go deleted file mode 100644 index 7b372d3be..000000000 --- a/internal/app/download_dependencies.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package app - -import ( - "errors" - "fmt" - "path/filepath" - "reflect" - "strings" - - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/netx" - "github.com/sighupio/furyctl/internal/tools" -) - -var ( - ErrDownloadingModule = errors.New("error downloading module") - ErrUnsupportedTools = errors.New("unsupported tools") - ErrModuleHasNoVersion = errors.New("module has no version") - ErrModuleHasNoName = errors.New("module has no name") -) - -type DownloadDependenciesRequest struct { - FuryctlBinVersion string - DistroLocation string - FuryctlConfPath string - Debug bool -} - -type DownloadDependenciesResponse struct { - DepsErrors []error - UnsupTools []string - RepoPath string -} - -func (v DownloadDependenciesResponse) HasErrors() bool { - return len(v.DepsErrors) > 0 -} - -func NewDownloadDependencies(client netx.Client, basePath string) *DownloadDependencies { - return &DownloadDependencies{ - client: client, - basePath: basePath, - } -} - -type DownloadDependencies struct { - client netx.Client - toolFactory tools.Factory - basePath string -} - -func (dd *DownloadDependencies) Execute(req DownloadDependenciesRequest) (DownloadDependenciesResponse, error) { - dloader := distribution.NewDownloader(dd.client, req.Debug) - - dres, err := dloader.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath) - if err != nil { - return DownloadDependenciesResponse{}, err - } - - errs := []error{} - if err := dd.DownloadModules(dres.DistroManifest.Modules); err != nil { - errs = append(errs, err) - } - if err := dd.DownloadInstallers(dres.DistroManifest.Kubernetes); err != nil { - errs = append(errs, err) - } - ut, err := dd.DownloadTools(dres.DistroManifest.Tools) - if err != nil { - errs = append(errs, err) - } - - return DownloadDependenciesResponse{ - DepsErrors: errs, - UnsupTools: ut, - RepoPath: dres.RepoPath, - }, nil -} - -func (dd *DownloadDependencies) DownloadModules(modules distribution.ManifestModules) error { - newPrefix := "https://github.com/sighupio/kubernetes-fury" - oldPrefix := "https://github.com/sighupio/fury-kubernetes" - - mods := reflect.ValueOf(modules) - - for i := 0; i < mods.NumField(); i++ { - name := strings.ToLower(mods.Type().Field(i).Name) - version := mods.Field(i).Interface().(string) - - if name == "" { - return ErrModuleHasNoName - } - - if version == "" { - return fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) - } - - errors := []error{} - for _, prefix := range []string{oldPrefix, newPrefix} { - src := fmt.Sprintf("git::%s-%s.git?ref=%s", prefix, name, version) - - if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "modules", name)); err != nil { - errors = append(errors, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err)) - continue - } - - errors = []error{} - break - } - - if len(errors) > 0 { - return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, errors) - } - } - - return nil -} - -func (dd *DownloadDependencies) DownloadInstallers(installers distribution.ManifestKubernetes) error { - insts := reflect.ValueOf(installers) - - for i := 0; i < insts.NumField(); i++ { - name := strings.ToLower(insts.Type().Field(i).Name) - version := insts.Field(i).Interface().(distribution.ManifestProvider).Installer - - src := fmt.Sprintf("git::https://github.com/sighupio/fury-%s-installer?ref=%s", name, version) - - if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "installers", name)); err != nil { - return err - } - } - - return nil -} - -func (dd *DownloadDependencies) DownloadTools(tools distribution.ManifestTools) ([]string, error) { - tls := reflect.ValueOf(tools) - - unsupportedTools := []string{} - for i := 0; i < tls.NumField(); i++ { - name := strings.ToLower(tls.Type().Field(i).Name) - version := tls.Field(i).Interface().(string) - - tool := dd.toolFactory.Create(name, version) - if tool == nil { - unsupportedTools = append(unsupportedTools, name) - continue - } - - dst := filepath.Join(dd.basePath, "vendor", "bin") - - if err := dd.client.Download(tool.SrcPath(), dst); err != nil { - return unsupportedTools, err - } - - if err := tool.Rename(dst); err != nil { - return unsupportedTools, err - } - } - - return unsupportedTools, nil -} diff --git a/internal/app/download_dependencies_test.go b/internal/app/download_dependencies_test.go deleted file mode 100644 index 1187b3991..000000000 --- a/internal/app/download_dependencies_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build integration - -package app_test - -import ( - "os" - "path/filepath" - "testing" - - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/netx" -) - -func TestDownloadDependencies(t *testing.T) { - testCases := []struct { - desc string - setup func(t *testing.T) (string, string) - teardown func(t *testing.T, tmpDir string) - wantErr bool - wantDepsErr bool - wantFiles []string - }{ - { - desc: "success", - wantErr: false, - wantDepsErr: false, - wantFiles: []string{ - "vendor/bin/furyagent", - "vendor/bin/kubectl", - "vendor/bin/kustomize", - "vendor/bin/terraform", - "vendor/installers/eks/README.md", - "vendor/installers/eks/modules/eks/main.tf", - "vendor/installers/eks/modules/vpc-and-vpn/main.tf", - "vendor/modules/auth/README.md", - "vendor/modules/auth/katalog/gangway/kustomization.yaml", - "vendor/modules/dr/README.md", - "vendor/modules/dr/katalog/velero/velero-aws/kustomization.yaml", - "vendor/modules/ingress/README.md", - "vendor/modules/ingress/katalog/nginx/kustomization.yaml", - "vendor/modules/logging/README.md", - "vendor/modules/logging/katalog/configs/kustomization.yaml", - "vendor/modules/monitoring/README.md", - "vendor/modules/monitoring/katalog/configs/kustomization.yaml", - "vendor/modules/opa/README.md", - "vendor/modules/opa/katalog/gatekeeper/kustomization.yaml", - }, - }, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - basePath, err := os.MkdirTemp("", "furyctl-test-") - if err != nil { - t.Fatalf("error creating tmp dir for test: %v", err) - } - defer os.RemoveAll(basePath) - - t.Logf("basePath: %s", basePath) - - distroLocation, err := filepath.Abs("../../test/data/v1.23.3/distro") - if err != nil { - t.Fatal(err) - } - - vc := app.NewDownloadDependencies(netx.NewGoGetterClient(), basePath) - - res, err := vc.Execute(app.DownloadDependenciesRequest{ - FuryctlBinVersion: "unknown", - DistroLocation: distroLocation, - FuryctlConfPath: "../../test/data/v1.23.3/furyctl.yaml", - Debug: true, - }) - - if tC.wantErr && err == nil { - t.Error("expected error, got nil") - } - if !tC.wantErr && err != nil { - t.Errorf("unexpected error, got = %v", err) - } - - if tC.wantDepsErr && len(res.DepsErrors) == 0 { - t.Fatal("expected deps download error, got none") - } - if !tC.wantDepsErr && len(res.DepsErrors) != 0 { - t.Fatalf("unexpected deps download error, got = %v", res.DepsErrors) - } - - for _, f := range tC.wantFiles { - info, err := os.Stat(filepath.Join(basePath, f)) - if err != nil { - if os.IsNotExist(err) { - t.Errorf("expected file %s to exist, but it doesn't", f) - } else { - t.Fatalf("unexpected error with file '%s': %v", f, err) - } - - continue - } - - if info.IsDir() { - t.Errorf("expected '%s' to be a file, it's a directory instead", f) - } - } - }) - } -} diff --git a/internal/app/update.go b/internal/app/update.go index a672b11d0..6ea07ca3c 100644 --- a/internal/app/update.go +++ b/internal/app/update.go @@ -7,36 +7,43 @@ package app import ( "context" "encoding/json" + "fmt" "net/http" "time" ) type Release struct { + //nolint:tagliatelle // GitHub response's field has snake case. URL string `json:"html_url"` Version string `json:"name"` } -// GetLatestRelease fetches the latest release from the GitHub API +const ( + latestSource = "https://api.github.com/repos/sighupio/furyctl/releases/latest" + timeout = 30 * time.Second +) + +// GetLatestRelease fetches the latest release from the GitHub API. func GetLatestRelease() (Release, error) { var release Release - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/sighupio/furyctl/releases/latest", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, latestSource, nil) if err != nil { - return release, err + return release, fmt.Errorf("error creating request: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { - return release, err + return release, fmt.Errorf("error performing request: %w", err) } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return release, err + return release, fmt.Errorf("error decoding response: %w", err) } return release, nil diff --git a/internal/app/validate_config.go b/internal/app/validate_config.go deleted file mode 100644 index e6a03f371..000000000 --- a/internal/app/validate_config.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package app - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/merge" - "github.com/sighupio/furyctl/internal/netx" - "github.com/sighupio/furyctl/internal/osx" - "github.com/sighupio/furyctl/internal/schema/santhosh" - "github.com/sighupio/furyctl/internal/yaml" -) - -type ValidateConfigRequest struct { - FuryctlBinVersion string - DistroLocation string - FuryctlConfPath string - Debug bool -} - -type ValidateConfigResponse struct { - Error error - RepoPath string -} - -func (v ValidateConfigResponse) HasErrors() bool { - return v.Error != nil -} - -func NewValidateConfig(client netx.Client) *ValidateConfig { - return &ValidateConfig{ - client: client, - } -} - -type ValidateConfig struct { - client netx.Client -} - -func (vc *ValidateConfig) Execute(req ValidateConfigRequest) (ValidateConfigResponse, error) { - dloader := distribution.NewDownloader(vc.client, req.Debug) - - res, err := dloader.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath) - if err != nil { - return ValidateConfigResponse{}, err - } - - schemaPath, err := distribution.GetSchemaPath(res.RepoPath, res.MinimalConf) - if err != nil { - return ValidateConfigResponse{}, err - } - - defaultPath := distribution.GetDefaultPath(res.RepoPath) - - defaultedFuryctlConfPath, err := vc.mergeConfigAndDefaults(req.FuryctlConfPath, defaultPath) - if err != nil { - return ValidateConfigResponse{}, err - } - if !req.Debug { - defer osx.CleanupTempDir(filepath.Base(defaultedFuryctlConfPath)) - } - - schema, err := santhosh.LoadSchema(schemaPath) - if err != nil { - return ValidateConfigResponse{}, err - } - - conf, err := yaml.FromFileV3[any](defaultedFuryctlConfPath) - if err != nil { - return ValidateConfigResponse{}, err - } - - if err := schema.ValidateInterface(conf); err != nil { - return ValidateConfigResponse{ - RepoPath: res.RepoPath, - Error: err, - }, nil - } - - return ValidateConfigResponse{}, nil -} - -func (vc *ValidateConfig) mergeConfigAndDefaults(furyctlFilePath, defaultsFilePath string) (string, error) { - defaultsFile, err := yaml.FromFileV2[map[any]any](defaultsFilePath) - if err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrYamlUnmarshalFile, err) - } - - furyctlFile, err := yaml.FromFileV2[map[any]any](furyctlFilePath) - if err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrYamlUnmarshalFile, err) - } - - defaultsModel := merge.NewDefaultModel(defaultsFile, ".data") - distributionModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - - distroMerger := merge.NewMerger(defaultsModel, distributionModel) - - defaultedDistribution, err := distroMerger.Merge() - if err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrMergeDistroConfig, err) - } - - furyctlModel := merge.NewDefaultModel(furyctlFile, ".spec.distribution") - defaultedDistributionModel := merge.NewDefaultModel(defaultedDistribution, ".data") - - furyctlMerger := merge.NewMerger(furyctlModel, defaultedDistributionModel) - - defaultedFuryctl, err := furyctlMerger.Merge() - if err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrMergeCompleteConfig, err) - } - - outYaml, err := yaml.MarshalV2(defaultedFuryctl) - if err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrYamlMarshalFile, err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-defaulted-") - if err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrCreatingTempDir, err) - } - - confPath := filepath.Join(outDirPath, "config.yaml") - if err := os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - return "", fmt.Errorf("%w: %v", distribution.ErrWriteFile, err) - } - - return confPath, nil -} diff --git a/internal/app/validate_config_test.go b/internal/app/validate_config_test.go deleted file mode 100644 index f0bc1ec2f..000000000 --- a/internal/app/validate_config_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package app_test - -import ( - "errors" - "io/fs" - "os" - "path/filepath" - "testing" - - "github.com/santhosh-tekuri/jsonschema" - - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/netx" - "github.com/sighupio/furyctl/internal/yaml" -) - -var eksClusterJsonSchema = map[string]any{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/1.23.2/ekscluster-kfd-v1alpha2.json", - "type": "object", - "additionalProperties": false, - "properties": map[string]any{ - "apiVersion": map[string]any{ - "type": "string", - }, - "kind": map[string]any{ - "type": "string", - }, - "spec": map[string]any{ - "type": "object", - "additionalProperties": false, - "properties": map[string]any{ - "distributionVersion": map[string]any{ - "type": "string", - }, - "distribution": map[string]any{ - "type": "object", - "additionalProperties": false, - "properties": map[string]any{ - "modules": map[string]any{ - "type": "object", - "additionalProperties": false, - "properties": map[string]any{ - "ingress": map[string]any{ - "type": "object", - "additionalProperties": false, - "properties": map[string]any{ - "test": map[string]any{ - "type": "string", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -} - -func TestValidateConfig(t *testing.T) { - testCases := []struct { - desc string - setup func(t *testing.T) (string, string) - teardown func(t *testing.T, tmpDir string) - wantErr bool - wantErrVal any - wantErrType error - wantValidationErr bool - wantValidationErrVal any - }{ - { - desc: "furyctl.yaml not found", - setup: func(t *testing.T) (string, string) { - t.Helper() - - tmpDir := mkDirTemp(t, "furyctl-config-validation-") - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - return tmpDir, configFilePath - }, - teardown: func(t *testing.T, tmpDir string) { - t.Helper() - - rmDirTemp(t, tmpDir) - }, - wantErr: true, - wantErrVal: &fs.PathError{}, - }, - { - desc: "wrong distro location", - setup: func(t *testing.T) (string, string) { - t.Helper() - - tmpDir := mkDirTemp(t, "furyctl-config-validation-") - - configFilePath := filepath.Join(tmpDir, "furyctl.yaml") - - configYaml, err := yaml.MarshalV2(furyConfig) - if err != nil { - t.Fatalf("error marshaling config: %v", err) - } - - if err := os.WriteFile(configFilePath, configYaml, os.ModePerm); err != nil { - t.Fatalf("error writing config file: %v", err) - } - - return "file::/tmp/does-not-exist", configFilePath - }, - teardown: func(t *testing.T, tmpDir string) { - t.Helper() - - rmDirTemp(t, tmpDir) - }, - wantErr: true, - wantErrType: distribution.ErrDownloadingFolder, - }, - { - desc: "success", - setup: func(t *testing.T) (string, string) { - t.Helper() - - return setupDistroFolder(t, correctFuryctlDefaults, correctKFDConf) - }, - teardown: func(t *testing.T, tmpDir string) { - t.Helper() - - rmDirTemp(t, tmpDir) - }, - }, - { - desc: "failure", - setup: func(t *testing.T) (string, string) { - t.Helper() - - return setupDistroFolder(t, wrongFuryctlDefaults, correctKFDConf) - }, - teardown: func(t *testing.T, tmpDir string) { - t.Helper() - - rmDirTemp(t, tmpDir) - }, - wantValidationErr: true, - wantValidationErrVal: &jsonschema.ValidationError{}, - }, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - tmpDir, configFilePath := tC.setup(t) - - defer tC.teardown(t, tmpDir) - - vc := app.NewValidateConfig(netx.NewGoGetterClient()) - res, err := vc.Execute(app.ValidateConfigRequest{ - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - - if tC.wantErr && err == nil { - t.Error("expected error, got nil") - } - if !tC.wantErr && err != nil { - t.Errorf("unexpected error, got = %v", err) - } - - if tC.wantErrVal != nil && !errors.As(err, &tC.wantErrVal) { - t.Fatalf("got error = %v, want = %v", err, tC.wantErrVal) - } - if tC.wantErrType != nil && !errors.Is(err, tC.wantErrType) { - t.Fatalf("got error = %v, want = %v", err, tC.wantErrType) - } - - if tC.wantValidationErr && res.Error == nil { - t.Fatal("expected validation error, got nil") - } - if !tC.wantValidationErr && res.Error != nil { - t.Fatalf("unexpected validation error, got = %v", res.Error) - } - - if tC.wantValidationErrVal != nil && !errors.As(res.Error, &tC.wantValidationErrVal) { - t.Fatalf("got validation error = %v, want = %v", res.Error, tC.wantValidationErrVal) - } - }) - } -} diff --git a/internal/app/validate_dependencies.go b/internal/app/validate_dependencies.go deleted file mode 100644 index 97882e10b..000000000 --- a/internal/app/validate_dependencies.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package app - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/execx" - "github.com/sighupio/furyctl/internal/netx" -) - -var ( - ErrEmptyToolVersion = errors.New("empty tool version") - ErrMissingEnvVar = errors.New("missing environment variable") - ErrWrongToolVersion = errors.New("wrong tool version") -) - -type ValidateDependenciesRequest struct { - BinPath string - FuryctlBinVersion string - DistroLocation string - FuryctlConfPath string - Debug bool -} - -type ValidateDependenciesResponse struct { - Errors []error - RepoPath string -} - -func (vdr *ValidateDependenciesResponse) appendErrors(errs []error) { - vdr.Errors = append(vdr.Errors, errs...) -} - -func (vdr *ValidateDependenciesResponse) HasErrors() bool { - return len(vdr.Errors) > 0 -} - -func NewValidateDependencies(client netx.Client, executor execx.Executor) *ValidateDependencies { - return &ValidateDependencies{ - client: client, - executor: executor, - } -} - -type ValidateDependencies struct { - client netx.Client - executor execx.Executor -} - -func (vd *ValidateDependencies) Execute(req ValidateDependenciesRequest) (ValidateDependenciesResponse, error) { - dloader := distribution.NewDownloader(vd.client, req.Debug) - - res := ValidateDependenciesResponse{} - - dres, err := dloader.Download(req.FuryctlBinVersion, req.DistroLocation, req.FuryctlConfPath) - if err != nil { - return res, err - } - - res.RepoPath = dres.RepoPath - res.appendErrors(vd.validateSystemDependencies(dres.DistroManifest, req.BinPath)) - res.appendErrors(vd.validateEnvVarsDependencies(dres.MinimalConf.Kind)) - - return res, nil -} - -func (vd *ValidateDependencies) validateEnvVarsDependencies(kind distribution.Kind) []error { - errs := make([]error, 0) - - if kind.Equals(distribution.EKSCluster) { - if os.Getenv("AWS_ACCESS_KEY_ID") == "" { - errs = append(errs, fmt.Errorf("%w: AWS_ACCESS_KEY_ID", ErrMissingEnvVar)) - } - - if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { - errs = append(errs, fmt.Errorf("%w: AWS_SECRET_ACCESS_KEY", ErrMissingEnvVar)) - } - - if os.Getenv("AWS_DEFAULT_REGION") == "" { - errs = append(errs, fmt.Errorf("%w: AWS_DEFAULT_REGION", ErrMissingEnvVar)) - } - } - - if len(errs) > 0 { - return errs - } - - return nil -} - -func (vd *ValidateDependencies) validateSystemDependencies(kfdManifest distribution.Manifest, binPath string) []error { - errs := make([]error, 0) - - if err := vd.checkAnsibleVersion(kfdManifest.Tools.Ansible, binPath); err != nil { - errs = append(errs, err) - } - - if err := vd.checkTerraformVersion(kfdManifest.Tools.Terraform, binPath); err != nil { - errs = append(errs, err) - } - - if err := vd.checkKubectlVersion(kfdManifest.Tools.Kubectl, binPath); err != nil { - errs = append(errs, err) - } - - if err := vd.checkKustomizeVersion(kfdManifest.Tools.Kustomize, binPath); err != nil { - errs = append(errs, err) - } - - if kfdManifest.Tools.Furyagent != "" { - if err := vd.checkFuryagentVersion(kfdManifest.Tools.Furyagent, binPath); err != nil { - errs = append(errs, err) - } - } - - if len(errs) > 0 { - return errs - } - - return nil -} - -func (vd *ValidateDependencies) checkAnsibleVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("ansible: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "ansible") - out, err := vd.executor.Command(path, "--version").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("ansible \\[.*]") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get ansible version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, " ") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get ansible version from system") - } - - systemAnsibleVersion := strings.TrimRight(versionStringTokens[len(versionStringTokens)-1], "]") - - if systemAnsibleVersion != wantVer { - return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemAnsibleVersion, wantVer) - } - - return nil -} - -func (vd *ValidateDependencies) checkTerraformVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("terraform: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "terraform") - out, err := vd.executor.Command(path, "--version").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("Terraform .*") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get terraform version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, " ") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get terraform version from system") - } - - systemTerraformVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") - - if systemTerraformVersion != wantVer { - return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemTerraformVersion, wantVer) - } - - return nil -} - -func (vd *ValidateDependencies) checkKubectlVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("kubectl: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "kubectl") - out, err := vd.executor.Command(path, "version", "--client").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("GitVersion:\"([^\"]*)\"") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get kubectl version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, ":") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get kubectl version from system") - } - - systemKubectlVersion := strings.TrimRight( - strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "\"v"), - "\"", - ) - - if systemKubectlVersion != wantVer { - return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemKubectlVersion, wantVer) - } - - return nil -} - -func (vd *ValidateDependencies) checkKustomizeVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("kustomize: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "kustomize") - out, err := vd.executor.Command(path, "version", "--short").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("kustomize/v(\\S*)") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get kustomize version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, "/") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get kustomize version from system") - } - - systemKustomizeVersion := strings.TrimLeft(versionStringTokens[len(versionStringTokens)-1], "v") - - if systemKustomizeVersion != wantVer { - return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemKustomizeVersion, wantVer) - } - - return nil -} - -func (vd *ValidateDependencies) checkFuryagentVersion(wantVer, binPath string) error { - if wantVer == "" { - return fmt.Errorf("furyagent: %w", ErrEmptyToolVersion) - } - - path := filepath.Join(binPath, "furyagent") - out, err := vd.executor.Command(path, "version").Output() - if err != nil { - return err - } - - s := string(out) - - pattern := regexp.MustCompile("version (\\S*)") - - versionStringIndex := pattern.FindStringIndex(s) - if versionStringIndex == nil { - return fmt.Errorf("can't get furyagent version from system") - } - - versionString := s[versionStringIndex[0]:versionStringIndex[1]] - - versionStringTokens := strings.Split(versionString, " ") - if len(versionStringTokens) == 0 { - return fmt.Errorf("can't get furyagent version from system") - } - - systemFuryagentVersion := versionStringTokens[len(versionStringTokens)-1] - - if systemFuryagentVersion != wantVer { - return fmt.Errorf("%w: installed = %s, expected = %s", ErrWrongToolVersion, systemFuryagentVersion, wantVer) - } - - return nil -} diff --git a/internal/app/validate_dependencies_test.go b/internal/app/validate_dependencies_test.go deleted file mode 100644 index d067baec7..000000000 --- a/internal/app/validate_dependencies_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package app_test - -import ( - "errors" - "fmt" - "io/fs" - "os" - "path/filepath" - "testing" - - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/execx" - "github.com/sighupio/furyctl/internal/netx" -) - -func TestValidateDependencies(t *testing.T) { - testCases := []struct { - desc string - client netx.Client - executor execx.Executor - envs map[string]string - kfdConf distribution.Manifest - wantErrCount int - wantErrVal any - wantErrType error - }{ - { - desc: "missing tools and envs", - client: netx.NewGoGetterClient(), - executor: execx.NewStdExecutor(), - kfdConf: correctKFDConf, - wantErrCount: 8, - wantErrVal: &fs.PathError{}, - wantErrType: app.ErrMissingEnvVar, - }, - { - desc: "has all tools and envs", - client: netx.NewGoGetterClient(), - executor: execx.NewFakeExecutor(), - kfdConf: correctKFDConf, - envs: map[string]string{ - "AWS_ACCESS_KEY_ID": "test", - "AWS_SECRET_ACCESS_KEY": "test", - "AWS_DEFAULT_REGION": "test", - }, - wantErrCount: 0, - }, - { - desc: "has wrong tools", - client: netx.NewGoGetterClient(), - executor: execx.NewFakeExecutor(), - kfdConf: wrongKFDConf, - envs: map[string]string{ - "AWS_ACCESS_KEY_ID": "test", - "AWS_SECRET_ACCESS_KEY": "test", - "AWS_DEFAULT_REGION": "test", - }, - wantErrCount: 5, - wantErrType: app.ErrWrongToolVersion, - }, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - tmpDir, configFilePath := setupDistroFolder(t, correctFuryctlDefaults, tC.kfdConf) - defer rmDirTemp(t, tmpDir) - - for k, v := range tC.envs { - t.Setenv(k, v) - } - - vd := app.NewValidateDependencies(tC.client, tC.executor) - - res, err := vd.Execute(app.ValidateDependenciesRequest{ - BinPath: filepath.Join(tmpDir, "bin"), - FuryctlBinVersion: "unknown", - DistroLocation: tmpDir, - FuryctlConfPath: configFilePath, - Debug: true, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if tC.wantErrCount != len(res.Errors) { - t.Errorf("Expected %d validation errors, got %d", tC.wantErrCount, len(res.Errors)) - for _, err := range res.Errors { - t.Log(err) - } - } - - for _, err := range res.Errors { - notErrAs := tC.wantErrVal != nil && !errors.As(err, &tC.wantErrVal) - notErrIs := tC.wantErrType != nil && !errors.Is(err, tC.wantErrType) - - if notErrAs && notErrIs { - t.Fatalf("got error = %v, want = %v", err, tC.wantErrVal) - } - } - }) - } -} - -func TestHelperProcess(t *testing.T) { - args := os.Args - - if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { - return - } - - cmd, _ := args[3], args[4:] - - switch cmd { - case "ansible": - fmt.Fprintf(os.Stdout, "ansible [core 2.11.2]\n "+ - "config file = None\n "+ - "configured module search path = ['', '']\n"+ - "ansible python module location = ./ansible\n"+ - "ansible collection location = ./ansible/collections\n"+ - "executable location = ./bin/ansible\n "+ - "python version = 3.9.14\n"+ - "jinja version = 3.1.2\n"+ - "libyaml = True\n") - case "terraform": - fmt.Fprintf(os.Stdout, "Terraform v0.15.4\non darwin_amd64") - case "kubectl": - fmt.Fprintf(os.Stdout, "Client Version: version.Info{Major:\"1\", "+ - "Minor:\"21\", GitVersion:\"v1.21.1\", GitCommit:\"xxxxx\", "+ - "GitTreeState:\"clean\", BuildDate:\"2021-05-12T14:00:00Z\", "+ - "GoVersion:\"go1.16.4\", Compiler:\"gc\", Platform:\"darwin/amd64\"}\n") - case "kustomize": - fmt.Fprintf(os.Stdout, "Version: {kustomize/v3.9.4 GitCommit:xxxxxxx"+ - "BuildDate:2021-05-12T14:00:00Z GoOs:darwin GoArch:amd64}") - case "furyagent": - fmt.Fprintf(os.Stdout, "furyagent version 0.3.0") - default: - fmt.Fprintf(os.Stdout, "command not found") - } - - os.Exit(0) -} diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go deleted file mode 100644 index 6f6abf32b..000000000 --- a/internal/bootstrap/bootstrap.go +++ /dev/null @@ -1,448 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package bootstrap - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/briandowns/spinner" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - "github.com/sighupio/furyctl/internal/configuration" - "github.com/sighupio/furyctl/internal/project" - "github.com/sighupio/furyctl/internal/provisioners" - "github.com/sighupio/furyctl/internal/terraform" -) - -const initExecutorMessage = " Initializing the terraform executor" - -// List of default subdirectories needed to run any provisioner. -var bootstrapProjectDefaultSubDirs = []string{"logs", "configuration", "output", "bin", "secrets"} - -// Bootstrap Represents the possible actions that can be made via CLI after some simple validations -type Bootstrap struct { - options *Options - s *spinner.Spinner - - project *project.Project - provisioner *provisioners.Provisioner -} - -// Options are valid configuration needed to proceed with the bootstrap management -type Options struct { - Spin *spinner.Spinner - Project *project.Project - ProvisionerConfiguration *configuration.Configuration - TerraformOpts *terraform.Options -} - -// New builds a Bootstrap object with some configurations using Options -func New(opts *Options) (b *Bootstrap, err error) { - // Grab the right provisioner - p, err := provisioners.Get(*opts.ProvisionerConfiguration) - if err != nil { - logrus.Errorf("Error creating the bootstrap instance while acquiring the right provisioner: %v", err) - return nil, err - } - if p.Enterprise() && opts.TerraformOpts.GitHubToken == "" { - logrus.Warningf("The %v provisioner is an enterprise feature and requires a valid GitHub token", opts.ProvisionerConfiguration.Provisioner) - } - b = &Bootstrap{ - s: opts.Spin, - options: opts, - project: opts.Project, - provisioner: &p, - } - b.options.TerraformOpts.Version = "0.15.4" - b.options.TerraformOpts.LogDir = "logs" - b.options.TerraformOpts.ConfigDir = "configuration" - if opts.ProvisionerConfiguration.Executor.StateConfiguration.Backend == "" { // The default should be a local file - opts.ProvisionerConfiguration.Executor.StateConfiguration.Backend = "local" - } - b.options.TerraformOpts.Backend = opts.ProvisionerConfiguration.Executor.StateConfiguration.Backend - b.options.TerraformOpts.BackendConfig = opts.ProvisionerConfiguration.Executor.StateConfiguration.Config - - return b, nil -} - -// Init intializes a project directory with all files (terraform project, subdirectories...) running terraform init on it -func (c *Bootstrap) Init(reset bool) (err error) { - prov := *c.provisioner - - // Enterprise token validation - if prov.Enterprise() && c.options.TerraformOpts.GitHubToken == "" { - errorMsg := fmt.Sprintf("error while initiating the bootstap process. The %v provisioner is an enterprise feature and requires a valid GitHub token. Contact sales@sighup.io", c.options.ProvisionerConfiguration.Provisioner) - logrus.Error(errorMsg) - return errors.New(errorMsg) - } - - // Reset the project directory - if reset { - logrus.Warn("Cleaning up the workdir") - err = c.project.Reset() - if err != nil { - logrus.Errorf("Error cleaning up the workdir") - return err - } - } - - // Project structure - c.s.Stop() - c.s.Suffix = " Creating project structure" - c.s.Start() - err = c.project.CreateSubDirs(bootstrapProjectDefaultSubDirs) - if err != nil { - logrus.Errorf("error while initializing project subdirectories: %v", err) - return err - } - - // .gitignore and .gitattributes - err = c.createGitFiles() - if err != nil { - logrus.Errorf("error while initializing project git files: %v", err) - return err - } - - // terraform executor - c.s.Stop() - c.s.Suffix = initExecutorMessage - c.s.Start() - err = c.initTerraformExecutor() - - if err != nil { - logrus.Errorf("error while initializing terraform executor: %v", err) - return err - } - - // Install the provisioner files into the project structure - c.s.Stop() - c.s.Suffix = " Installing provisioner terraform files" - c.s.Start() - err = c.installProvisionerTerraformFiles() - if err != nil { - logrus.Errorf("error while copying terraform project from the provisioner to the project dir: %v", err) - return err - } - - // Init the terraform project - tf := prov.TerraformExecutor() - c.s.Stop() - c.s.Suffix = " Initializing terraform project" - c.s.Start() - - err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) - if err != nil { - logrus.Errorf("error while running terraform init in the project dir: %v", err) - return err - } - c.s.Stop() - c.postInit() - return nil -} - -func (c *Bootstrap) postInit() { - prov := *c.provisioner - fmt.Printf(`%v -[FURYCTL] - -Init phase completed. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs - -Everything ready to create the infrastructure; execute: - -$ furyctl bootstrap apply - -`, prov.InitMessage(), c.project.Path, c.project.Path) -} - -func (c *Bootstrap) postUpdate() { - proj := *c.project - prov := *c.provisioner - fmt.Printf(`%v -[FURYCTL] -Apply phase completed. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs -Output file: %v/output/output.json - -Everything is up to date. -Ready to apply or destroy the infrastructure; execute: - -$ furyctl bootstrap apply -or -$ furyctl bootstrap destroy - -`, prov.UpdateMessage(), proj.Path, proj.Path, proj.Path) -} - -func (c *Bootstrap) postPlan() { - proj := *c.project - fmt.Printf(`[FURYCTL] -Apply (dryrun) phase completed. -Discover the upcoming changes in the terraform log file. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs - -Ready to apply or destroy the infrastructure; execute: - -$ furyctl bootstrap apply -or -$ furyctl bootstrap destroy - -`, proj.Path, proj.Path) -} - -func (c *Bootstrap) postDestroy() { - prov := *c.provisioner - proj := *c.project - fmt.Printf(`%v -[FURYCTL] -Destroy phase completed. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs - -`, prov.DestroyMessage(), proj.Path, proj.Path) -} - -// Update updates the bootstrap (terraform apply) -func (c *Bootstrap) Update(dryrun bool) (err error) { - // Project structure - c.s.Stop() - c.s.Suffix = " Updating project structure" - c.s.Start() - err = c.project.CreateSubDirs(bootstrapProjectDefaultSubDirs) - if err != nil { - logrus.Warnf("error while updating project subdirectories: %v", err) - } - - // .gitignore and .gitattributes - err = c.createGitFiles() - if err != nil { - logrus.Errorf("error while initializing project git files: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = initExecutorMessage - c.s.Start() - err = c.initTerraformExecutor() - if err != nil { - logrus.Errorf("Error while initializing the terraform executor: %v", err) - return err - } - - // Install the provisioner files into the project structure - c.s.Stop() - c.s.Suffix = " Updating provisioner terraform files" - c.s.Start() - err = c.installProvisionerTerraformFiles() - if err != nil { - logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) - } - - prov := *c.provisioner - c.s.Stop() - - // Init the terraform project - tf := prov.TerraformExecutor() - c.s.Suffix = " Re-Initializing terraform project" - c.s.Start() - - err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend), tfexec.Upgrade(c.options.TerraformOpts.UpgradeDeps)) - if err != nil { - logrus.Errorf("error while running terraform init in the project dir: %v", err) - return err - } - - if !dryrun { - c.s.Suffix = " Applying terraform project" - c.s.Start() - _, err = prov.Update() - if err != nil { - logrus.Errorf("Error while updating the bootstrap. Take a look to the logs. %v", err) - return err - } - c.s.Stop() - c.s.Suffix = " Saving outputs" - c.s.Start() - var output []byte - output, err = c.output() - if err != nil { - logrus.Errorf("Error while getting the output with the bootstrap data: %v", err) - return err - } - - proj := *c.project - err = proj.WriteFile("output/output.json", output) - if err != nil { - logrus.Errorf("Error while writing the output.json to the project directory: %v", err) - return err - } - c.s.Stop() - c.postUpdate() - } else { - c.s.Suffix = " [DRYRUN] Applying terraform project" - c.s.Start() - err = prov.Plan() - if err != nil { - logrus.Errorf("[DRYRUN] Error while updating the bootstrap. Take a look to the logs. %v", err) - return err - } - c.s.Stop() - proj := *c.project - logrus.Infof("[DRYRUN] Discover the resulting plan in the %v/logs/terraform.logs file", proj.Path) - c.postPlan() - } - return nil -} - -// Destroy destroys the bootstrap (terraform destroy) -func (c *Bootstrap) Destroy() (err error) { - // Project structure - c.s.Stop() - c.s.Suffix = " Updating project structure" - c.s.Start() - err = c.project.CreateSubDirs(bootstrapProjectDefaultSubDirs) - if err != nil { - logrus.Warnf("error while updating project subdirectories: %v", err) - } - - // .gitignore and .gitattributes - err = c.createGitFiles() - if err != nil { - logrus.Errorf("error while initializing project git files: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = initExecutorMessage - c.s.Start() - err = c.initTerraformExecutor() - if err != nil { - logrus.Errorf("Error while initializing the terraform executor: %v", err) - return err - } - - // Install the provisioner files into the project structure - c.s.Stop() - c.s.Suffix = " Updating provisioner terraform files" - c.s.Start() - err = c.installProvisionerTerraformFiles() - if err != nil { - logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) - } - - prov := *c.provisioner - c.s.Stop() - - // Init the terraform project - tf := prov.TerraformExecutor() - c.s.Suffix = " Re-Initializing terraform project" - c.s.Start() - - err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) - if err != nil { - logrus.Errorf("error while running terraform init in the project dir: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = " Destroying terraform project" - c.s.Start() - err = prov.Destroy() - if err != nil { - logrus.Errorf("Error while destroying the bootstrap. Take a look to the logs. %v", err) - return err - } - c.s.Stop() - c.postDestroy() - return nil -} - -// installs/copy files from the provisioner to the working dir -func (c *Bootstrap) installProvisionerTerraformFiles() (err error) { - proj := *c.project - prov := *c.provisioner - b := prov.Box() - for _, tfFileName := range prov.TerraformFiles() { - tfFile, err := b.Find(tfFileName) - if err != nil { - logrus.Errorf("Error while finding the right file in the box: %v", err) - return err - } - err = proj.WriteFile(tfFileName, tfFile) - if err != nil { - logrus.Errorf("Error while writing the binary data from the box to the project dir: %v", err) - return err - } - } - return nil -} - -// creates the terraform executor to being used by the bootstrap instance and its provisioner -func (c *Bootstrap) initTerraformExecutor() (err error) { - tf, err := terraform.NewExecutor(*c.options.TerraformOpts) - if err != nil { - logrus.Errorf("Error while initializing the terraform executor: %v", err) - return err - } - - // Attach the terraform executor to the provisioner - prov := *c.provisioner - prov.SetTerraformExecutor(tf) - return nil -} - -// Output gathers the Output in form of binary data -func (c *Bootstrap) output() ([]byte, error) { - prov := *c.provisioner - logrus.Info("Gathering output file as json") - var output map[string]tfexec.OutputMeta - output, err := prov.TerraformExecutor().Output(context.Background()) - if err != nil { - logrus.Fatalf("Error while getting project output: %v", err) - return nil, err - } - return json.MarshalIndent(output, "", " ") -} - -func (c *Bootstrap) createGitFiles() error { - c.s.Stop() - c.s.Suffix = " Creating .gitattributes file" - c.s.Start() - gitattributes := `*secrets/** filter=git-crypt diff=git-crypt -*output/** filter=git-crypt diff=git-crypt -*logs/** filter=git-crypt diff=git-crypt -*configuration/** filter=git-crypt diff=git-crypt -` - err := c.project.WriteFile(".gitattributes", []byte(gitattributes)) - if err != nil { - logrus.Errorf("error while creating .gitattributes: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = " Creating .gitignore file" - c.s.Start() - gitignore := `.terraform -bin -` - err = c.project.WriteFile(".gitignore", []byte(gitignore)) - if err != nil { - logrus.Errorf("error while creating .gitignore: %v", err) - return err - } - - return nil -} diff --git a/internal/bootstrap/configuration/aws.go b/internal/bootstrap/configuration/aws.go deleted file mode 100644 index de492e98e..000000000 --- a/internal/bootstrap/configuration/aws.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -// AWS represents the configuration spec of a AWS bootstrap project including VPC and VPN -type AWS struct { - NetworkCIDR string `yaml:"networkCIDR"` - PublicSubnetsCIDRs []string `yaml:"publicSubnetsCIDRs"` - PrivateSubnetsCIDRs []string `yaml:"privateSubnetsCIDRs"` - VPN AWSVPN `yaml:"vpn"` - Tags map[string]string `yaml:"tags"` -} - -// AWSVPN represents an VPN configuration -type AWSVPN struct { - Instances int `yaml:"instances"` - Port int `yaml:"port"` - InstanceType string `yaml:"instanceType"` - DiskSize int `yaml:"diskSize"` - OperatorName string `yaml:"operatorName"` - DHParamsBits int `yaml:"dhParamsBits"` - SubnetCIDR string `yaml:"subnetCIDR"` - SSHUsers []string `yaml:"sshUsers"` - OperatorCIDRs []string `yaml:"operatorCIDRs"` -} diff --git a/internal/bootstrap/provisioners/aws/provisioner.go b/internal/bootstrap/provisioners/aws/provisioner.go deleted file mode 100644 index 27a47da6e..000000000 --- a/internal/bootstrap/provisioners/aws/provisioner.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aws - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "strings" - - "github.com/gobuffalo/packr/v2" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - cfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - "github.com/sighupio/furyctl/internal/configuration" -) - -// InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated -func (d *AWS) InitMessage() string { - return `[AWS] - VPC and VPN - -This provisioner creates a battle-tested AWS VPC with all the requirements -set to run a production-grade private EKS cluster. - -It creates a VPN server enables deploying the cluster from this computer -once connected to the VPN server. - -Then, use furyagent to manage VPN profiles. -` -} - -// UpdateMessage return a custom provisioner message the user will see once the cluster is updated -func (d *AWS) UpdateMessage() string { - var output map[string]tfexec.OutputMeta - output, err := d.terraform.Output(context.Background()) - if err != nil { - logrus.Error("Can not get output values") - } - spec := d.config.Spec.(cfg.AWS) - sshUsers := spec.VPN.SSHUsers - var vpnOperatorName, vpcID string - var vpnInstanceIPs, publicSubnetsIDs, privateSubnetsIDs []string - err = json.Unmarshal(output["vpn_ip"].Value, &vpnInstanceIPs) - if err != nil { - logrus.Error("Can not get `vpn_ip` value") - } - err = json.Unmarshal(output["vpn_operator_name"].Value, &vpnOperatorName) - if err != nil { - logrus.Error("Can not get `vpn_operator_name` value") - } - err = json.Unmarshal(output["vpc_id"].Value, &vpcID) - if err != nil { - logrus.Error("Can not get `vpc_id` value") - } - err = json.Unmarshal(output["public_subnets"].Value, &publicSubnetsIDs) - if err != nil { - logrus.Error("Can not get `public_subnets` value") - } - err = json.Unmarshal(output["private_subnets"].Value, &privateSubnetsIDs) - if err != nil { - logrus.Error("Can not get `private_subnets` value") - } - - vpnFragment := "" - if len(vpnInstanceIPs) > 0 { - vpnSSHFragment := "" - for _, server := range vpnInstanceIPs { - vpnSSHFragment = vpnSSHFragment + fmt.Sprintf("$ ssh %v@%v\n", vpnOperatorName, server) - } - vpnFragment = fmt.Sprintf(` -Your VPN instance IPs are: %v -Use the ssh %v username to access the VPN instance with any SSH key configured -for the following GitHub users: %v. - -%v`, vpnInstanceIPs, vpnOperatorName, sshUsers, vpnSSHFragment) - } - - return fmt.Sprintf(`[AWS] - VPC and VPN - -All the bootstrap components are up to date. - -VPC and VPN ready: - -VPC: %v -Public Subnets: %v -Private Subnets: %v -%v -Then create a openvpn configuration (ovpn) file using the furyagent cli: - -$ furyagent configure openvpn-client --client-name --config %v/secrets/furyagent.yml > .ovpn - -Discover already registered vpn clients running: - -$ furyagent configure openvpn-client --list --config %v/secrets/furyagent.yml - -IMPORTANT! Connect to the VPN with the created ovpn profile to continue deploying -an AWS Kubernetes cluster. -`, vpcID, publicSubnetsIDs, privateSubnetsIDs, vpnFragment, d.terraform.WorkingDir(), d.terraform.WorkingDir()) -} - -// DestroyMessage return a custom provisioner message the user will see once the cluster is destroyed -func (d *AWS) DestroyMessage() string { - return `[AWS] - VPC and VPN -All bootstrap components were destroyed. -VPN and VPC went away. - -Had problems, contact us at sales@sighup.io. -` -} - -// Enterprise return a boolean indicating it is an enterprise provisioner -func (d *AWS) Enterprise() bool { - return false -} - -// AWS represents a dummy provisioner -type AWS struct { - terraform *tfexec.Terraform - box *packr.Box - config *configuration.Configuration -} - -const ( - projectPath = "../../../../data/provisioners/bootstrap/aws" -) - -func (d AWS) createVarFile() (err error) { - var buffer bytes.Buffer - spec := d.config.Spec.(cfg.AWS) - - buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", d.config.Metadata.Name)) - buffer.WriteString(fmt.Sprintf("network_cidr = \"%v\"\n", spec.NetworkCIDR)) - buffer.WriteString(fmt.Sprintf("public_subnetwork_cidrs = [\"%v\"]\n", strings.Join(spec.PublicSubnetsCIDRs, "\",\""))) - buffer.WriteString(fmt.Sprintf("private_subnetwork_cidrs = [\"%v\"]\n", strings.Join(spec.PrivateSubnetsCIDRs, "\",\""))) - buffer.WriteString(fmt.Sprintf("vpn_subnetwork_cidr = \"%v\"\n", spec.VPN.SubnetCIDR)) - if len(spec.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(spec.Tags) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) - } - buffer.WriteString(fmt.Sprintf("vpn_instances = %v\n", spec.VPN.Instances)) - if spec.VPN.Port != 0 { - buffer.WriteString(fmt.Sprintf("vpn_port = %v\n", spec.VPN.Port)) - } - if spec.VPN.InstanceType != "" { - buffer.WriteString(fmt.Sprintf("vpn_instance_type = \"%v\"\n", spec.VPN.InstanceType)) - } - if spec.VPN.DiskSize != 0 { - buffer.WriteString(fmt.Sprintf("vpn_instance_disk_size = %v\n", spec.VPN.DiskSize)) - } - if spec.VPN.OperatorName != "" { - buffer.WriteString(fmt.Sprintf("vpn_operator_name = \"%v\"\n", spec.VPN.OperatorName)) - } - if spec.VPN.DHParamsBits != 0 { - buffer.WriteString(fmt.Sprintf("vpn_dhparams_bits = %v\n", spec.VPN.DHParamsBits)) - } - if len(spec.VPN.OperatorCIDRs) != 0 { - buffer.WriteString(fmt.Sprintf("vpn_operator_cidrs = [\"%v\"]\n", strings.Join(spec.VPN.OperatorCIDRs, "\",\""))) - } - if len(spec.VPN.SSHUsers) != 0 { - buffer.WriteString(fmt.Sprintf("vpn_ssh_users = [\"%v\"]\n", strings.Join(spec.VPN.SSHUsers, "\",\""))) - } - - err = ioutil.WriteFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()), buffer.Bytes(), 0o600) - if err != nil { - return err - } - err = d.terraform.FormatWrite(context.Background(), tfexec.Dir(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) - if err != nil { - return err - } - return nil -} - -// New instantiates a new AWS provisioner -func New(config *configuration.Configuration) *AWS { - b := packr.New("AWS", projectPath) - return &AWS{ - box: b, - config: config, - } -} - -// SetTerraformExecutor adds the terraform executor to this provisioner -func (d *AWS) SetTerraformExecutor(tf *tfexec.Terraform) { - d.terraform = tf -} - -// TerraformExecutor returns the current terraform executor of this provisioner -func (d *AWS) TerraformExecutor() (tf *tfexec.Terraform) { - return d.terraform -} - -// Box returns the box that has the files as binary data -func (d AWS) Box() *packr.Box { - return d.box -} - -// TerraformFiles returns the list of files conforming the terraform project -func (d AWS) TerraformFiles() []string { - // TODO understand if it is possible to deduce these values somehow - // find . -type f -follow -print - return []string{ - "output.tf", - "main.tf", - "variables.tf", - } -} - -// Plan runs a dry run execution -func (d AWS) Prepare() (err error) { - return nil -} - -// Plan runs a dry run execution -func (d AWS) Plan() (err error) { - logrus.Info("[DRYRUN] Updating AWS Bootstrap project") - err = d.createVarFile() - if err != nil { - return err - } - changes, err := d.terraform.Plan(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) - if err != nil { - logrus.Fatalf("[DRYRUN] Something went wrong while updating aws. %v", err) - return err - } - if changes { - logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") - } else { - logrus.Info("[DRYRUN] Everything is up to date") - } - - logrus.Info("[DRYRUN] AWS Updated") - return nil -} - -// Update runs terraform apply in the project -func (d AWS) Update() (string, error) { - logrus.Info("Updating AWS Bootstrap project") - err := d.createVarFile() - if err != nil { - return "", err - } - - err = d.terraform.Apply(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) - if err != nil { - logrus.Fatalf("Something went wrong while updating aws. %v", err) - return "", err - } - - logrus.Info("AWS Updated") - return "", nil -} - -// Destroy runs terraform destroy in the project -func (d AWS) Destroy() (err error) { - logrus.Info("Destroying AWS Bootstrap project") - err = d.createVarFile() - if err != nil { - return err - } - - err = d.terraform.Destroy(context.Background(), tfexec.VarFile(fmt.Sprintf("%v/aws.tfvars", d.terraform.WorkingDir()))) - if err != nil { - logrus.Fatalf("Something went wrong while destroying AWS Bootstrap project. %v", err) - return err - } - logrus.Info("AWS Bootstrap destroyed") - return nil -} diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go deleted file mode 100644 index b068351d9..000000000 --- a/internal/cluster/cluster.go +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cluster - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/briandowns/spinner" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - "github.com/sighupio/furyctl/internal/configuration" - "github.com/sighupio/furyctl/internal/project" - "github.com/sighupio/furyctl/internal/provisioners" - "github.com/sighupio/furyctl/internal/terraform" -) - -const initExecutorMessage = " Initializing the terraform executor" - -// List of default subdirectories needed to run any provisioner. -var clusterProjectDefaultSubDirs = []string{"logs", "configuration", "output", "bin", "secrets"} - -// Cluster Represents the possible actions that can be made via CLI after some simple validations -type Cluster struct { - options *Options - s *spinner.Spinner - - project *project.Project - provisioner *provisioners.Provisioner -} - -// Options are valid configuration needed to proceed with the cluster management -type Options struct { - Spin *spinner.Spinner - Project *project.Project - ProvisionerConfiguration *configuration.Configuration - TerraformOpts *terraform.Options -} - -// New builds a Cluster object with some configurations using Options -func New(opts *Options) (c *Cluster, err error) { - // Grab the right provisioner - p, err := provisioners.Get(*opts.ProvisionerConfiguration) - if err != nil { - logrus.Errorf("Error creating the cluster instance while acquiring the right provisioner: %v", err) - return nil, err - } - if p.Enterprise() && opts.TerraformOpts.GitHubToken == "" { - logrus.Warningf("The %v provisioner is an enterprise feature and requires a valid GitHub token", opts.ProvisionerConfiguration.Provisioner) - } - c = &Cluster{ - s: opts.Spin, - options: opts, - project: opts.Project, - provisioner: &p, - } - - c.options.TerraformOpts.Version = "0.15.4" - c.options.TerraformOpts.LogDir = "logs" - c.options.TerraformOpts.ConfigDir = "configuration" - if opts.ProvisionerConfiguration.Executor.StateConfiguration.Backend == "" { // The default should be a local file - opts.ProvisionerConfiguration.Executor.StateConfiguration.Backend = "local" - } - c.options.TerraformOpts.Backend = opts.ProvisionerConfiguration.Executor.StateConfiguration.Backend - c.options.TerraformOpts.BackendConfig = opts.ProvisionerConfiguration.Executor.StateConfiguration.Config - - return c, nil -} - -// Init intializes a project directory with all files (terraform project, subdirectories...) running terraform init on it -func (c *Cluster) Init(reset bool) (err error) { - prov := *c.provisioner - - // Enterprise token validation - if prov.Enterprise() && c.options.TerraformOpts.GitHubToken == "" { - errorMsg := fmt.Sprintf("error creating the cluster instance. The %v provisioner is an enterprise feature and requires a valid GitHub token. Contact sales@sighup.io", c.options.ProvisionerConfiguration.Provisioner) - logrus.Error(errorMsg) - return errors.New(errorMsg) - } - - // Reset the project directory - if reset { - logrus.Warn("Cleaning up the workdir") - err = c.project.Reset() - if err != nil { - logrus.Errorf("Error cleaning up the workdir") - return err - } - } - - // Project structure - c.s.Stop() - c.s.Suffix = " Creating project structure" - c.s.Start() - err = c.project.CreateSubDirs(clusterProjectDefaultSubDirs) - if err != nil { - logrus.Errorf("error while initializing project subdirectories: %v", err) - return err - } - - // .gitignore and .gitattributes - err = c.createGitFiles() - if err != nil { - logrus.Errorf("error while initializing project git files: %v", err) - return err - } - - // terraform executor - c.s.Stop() - c.s.Suffix = initExecutorMessage - c.s.Start() - err = c.initTerraformExecutor() - - if err != nil { - logrus.Errorf("error while initializing terraform executor: %v", err) - return err - } - - // Install the provisioner files into the project structure - c.s.Stop() - c.s.Suffix = " Installing provisioner terraform files" - c.s.Start() - err = c.installProvisionerTerraformFiles() - if err != nil { - logrus.Errorf("error while copying terraform project from the provisioner to the project dir: %v", err) - return err - } - - // Ask the provisioner to prepare its own environment - c.s.Stop() - c.s.Suffix = " Preparing the provisioner environment" - c.s.Start() - err = prov.Prepare() - if err != nil { - logrus.Errorf("error while preparing provisioner environment: %v", err) - return err - } - - // Init the terraform project - tf := prov.TerraformExecutor() - c.s.Stop() - c.s.Suffix = " Initializing terraform project" - c.s.Start() - - err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) - if err != nil { - logrus.Errorf("error while running terraform init in the project dir: %v", err) - return err - } - c.s.Stop() - c.postInit() - return nil -} - -func (c *Cluster) postInit() { - prov := *c.provisioner - fmt.Printf(`%v -[FURYCTL] - -Init phase completed. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs - -Everything ready to create the infrastructure; execute: - -$ furyctl cluster apply - -`, prov.InitMessage(), c.project.Path, c.project.Path) -} - -func (c *Cluster) postUpdate() { - proj := *c.project - prov := *c.provisioner - fmt.Printf(`%v -[FURYCTL] -Apply phase completed. The Kubernetes Cluster is up to date. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs -Output file: %v/output/output.json -Kubernetes configuration file: %v/secrets/kubeconfig - -Use it by running: -$ export KUBECONFIG=%v/secrets/kubeconfig -$ kubectl get nodes - -Everything is up to date. -Ready to apply or destroy the infrastructure; execute: - -$ furyctl cluster apply -or -$ furyctl cluster destroy - -`, prov.UpdateMessage(), proj.Path, proj.Path, proj.Path, proj.Path, proj.Path) -} - -func (c *Cluster) postPlan() { - proj := *c.project - fmt.Printf(`[FURYCTL] -Update (dryrun) phase completed. -Discover the upcoming changes in the terraform log file. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs - -Ready to apply or destroy the infrastructure; execute: - -$ furyctl cluster apply -or -$ furyctl cluster destroy - -`, proj.Path, proj.Path) -} - -func (c *Cluster) postDestroy() { - prov := *c.provisioner - proj := *c.project - fmt.Printf(`%v -[FURYCTL] -Destroy phase completed. - -Project directory: %v -Terraform logs: %v/logs/terraform.logs - -`, prov.DestroyMessage(), proj.Path, proj.Path) -} - -// Update updates the cluster (terraform apply) -func (c *Cluster) Update(dryrun bool) (err error) { - // Project structure - c.s.Stop() - c.s.Suffix = " Updating project structure" - c.s.Start() - err = c.project.CreateSubDirs(clusterProjectDefaultSubDirs) - if err != nil { - logrus.Warnf("error while initializing project subdirectories: %v", err) - } - - // .gitignore and .gitattributes - err = c.createGitFiles() - if err != nil { - logrus.Errorf("error while initializing project git files: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = initExecutorMessage - c.s.Start() - err = c.initTerraformExecutor() - if err != nil { - logrus.Errorf("Error while initializing the terraform executor: %v", err) - return err - } - - // Install the provisioner files into the project structure - c.s.Stop() - c.s.Suffix = " Updating provisioner terraform files" - c.s.Start() - err = c.installProvisionerTerraformFiles() - if err != nil { - logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) - } - - // Init the terraform project - prov := *c.provisioner - tf := prov.TerraformExecutor() - c.s.Stop() - c.s.Suffix = " Re-Initializing terraform project" - c.s.Start() - - err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend), tfexec.Upgrade(c.options.TerraformOpts.UpgradeDeps)) - if err != nil { - logrus.Errorf("Error while running terraform init in the project dir: %v", err) - return err - } - - c.s.Stop() - - if !dryrun { - c.s.Suffix = " Applying terraform project" - c.s.Start() - kubeconfig, err := prov.Update() - if err != nil { - logrus.Errorf("Error while updating the cluster. Take a look to the logs. %v", err) - return err - } - c.s.Stop() - c.s.Suffix = " Saving outputs" - c.s.Start() - var output []byte - output, err = c.output() - if err != nil { - logrus.Errorf("Error while getting the output with the cluster data: %v", err) - return err - } - - proj := *c.project - err = proj.WriteFile("output/output.json", output) - if err != nil { - logrus.Errorf("Error while writing the output.json to the project directory: %v", err) - return err - } - c.s.Stop() - - err = proj.WriteFile("secrets/kubeconfig", []byte(kubeconfig)) - if err != nil { - logrus.Errorf("Error while writing the kubeconfig to the project directory: %v", err) - return err - } - c.s.Stop() - - c.postUpdate() - } else { - c.s.Suffix = " [DRYRUN] Applying terraform project" - c.s.Start() - err = prov.Plan() - if err != nil { - logrus.Errorf("[DRYRUN] Error while updating the cluster. Take a look to the logs. %v", err) - return err - } - c.s.Stop() - proj := *c.project - logrus.Infof("[DRYRUN] Discover the resulting plan in the %v/logs/terraform.logs file", proj.Path) - c.postPlan() - } - return nil -} - -// Destroy destroys the cluster (terraform destroy) -func (c *Cluster) Destroy() (err error) { - // Project structure - c.s.Stop() - c.s.Suffix = " Updating project structure" - c.s.Start() - err = c.project.CreateSubDirs(clusterProjectDefaultSubDirs) - if err != nil { - logrus.Warnf("Error while initializing project subdirectories: %v", err) - } - - // .gitignore and .gitattributes - err = c.createGitFiles() - if err != nil { - logrus.Errorf("Error while initializing project git files: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = initExecutorMessage - c.s.Start() - err = c.initTerraformExecutor() - if err != nil { - logrus.Errorf("Error while initializing the terraform executor: %v", err) - return err - } - - // Install the provisioner files into the project structure - c.s.Stop() - c.s.Suffix = " Updating provisioner terraform files" - c.s.Start() - err = c.installProvisionerTerraformFiles() - if err != nil { - logrus.Warnf("error while copying terraform project from the provisioner to the project dir: %v", err) - } - - // Init the terraform project - prov := *c.provisioner - tf := prov.TerraformExecutor() - c.s.Stop() - c.s.Suffix = " Re-Initializing terraform project" - c.s.Start() - - err = tf.Init(context.Background(), tfexec.Reconfigure(c.options.TerraformOpts.ReconfigureBackend)) - if err != nil { - logrus.Errorf("error while running terraform init in the project dir: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = " Destroying terraform project" - c.s.Start() - err = prov.Destroy() - if err != nil { - logrus.Errorf("Error while destroying the cluster. Take a look to the logs. %v", err) - return err - } - c.s.Stop() - c.postDestroy() - return nil -} - -// installs/copy files from the provisioner to the working dir -func (c *Cluster) installProvisionerTerraformFiles() (err error) { - proj := *c.project - prov := *c.provisioner - b := prov.Box() - for _, tfFileName := range prov.TerraformFiles() { - tfFile, err := b.Find(tfFileName) - if err != nil { - logrus.Errorf("Error while finding the right file in the box: %v", err) - return err - } - err = proj.WriteFile(tfFileName, tfFile) - if err != nil { - logrus.Errorf("Error while writing the binary data from the box to the project dir: %v", err) - return err - } - } - return nil -} - -// creates the terraform executor to being used by the cluster instance and its provisioner -func (c *Cluster) initTerraformExecutor() (err error) { - tf, err := terraform.NewExecutor(*c.options.TerraformOpts) - if err != nil { - logrus.Errorf("Error while initializing the terraform executor: %v", err) - return err - } - - // Attach the terraform executor to the provisioner - prov := *c.provisioner - prov.SetTerraformExecutor(tf) - return nil -} - -// Output gathers the Output in form of binary data -func (c *Cluster) output() ([]byte, error) { - prov := *c.provisioner - logrus.Info("Gathering output file as json") - var output map[string]tfexec.OutputMeta - output, err := prov.TerraformExecutor().Output(context.Background()) - if err != nil { - logrus.Fatalf("Error while getting project output: %v", err) - return nil, err - } - return json.MarshalIndent(output, "", " ") -} - -func (c *Cluster) createGitFiles() error { - c.s.Stop() - c.s.Suffix = " Creating .gitattributes file" - c.s.Start() - gitattributes := `*secrets/** filter=git-crypt diff=git-crypt -*output/** filter=git-crypt diff=git-crypt -*logs/** filter=git-crypt diff=git-crypt -*configuration/** filter=git-crypt diff=git-crypt -` - err := c.project.WriteFile(".gitattributes", []byte(gitattributes)) - if err != nil { - logrus.Errorf("error while creating .gitattributes: %v", err) - return err - } - - c.s.Stop() - c.s.Suffix = " Creating .gitignore file" - c.s.Start() - gitignore := `.terraform -bin -` - err = c.project.WriteFile(".gitignore", []byte(gitignore)) - if err != nil { - logrus.Errorf("error while creating .gitignore: %v", err) - return err - } - - return nil -} diff --git a/internal/cluster/configuration/common.go b/internal/cluster/configuration/common.go deleted file mode 100644 index bb411e278..000000000 --- a/internal/cluster/configuration/common.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -// DMZCIDRRange represents a string or a list of strings -type DMZCIDRRange struct { - Values []string -} - -// UnmarshalYAML unmarshall the DMZCIDRRange -func (sm *DMZCIDRRange) UnmarshalYAML(unmarshal func(interface{}) error) error { - var multi []string - err := unmarshal(&multi) - if err != nil { - var single string - err := unmarshal(&single) - if err != nil { - return err - } - sm.Values = make([]string, 1) - sm.Values[0] = single - } else { - sm.Values = multi - } - return nil -} - -// MarshalYAML marshall the DMZCIDRRange -func (sm DMZCIDRRange) MarshalYAML() (interface{}, error) { - if len(sm.Values) == 1 { - return sm.Values[0], nil - } - - return sm.Values, nil -} diff --git a/internal/cluster/configuration/eks.go b/internal/cluster/configuration/eks.go deleted file mode 100644 index f0d6cae7e..000000000 --- a/internal/cluster/configuration/eks.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -// EKS represents the configuration spec of a EKS Cluster -type EKS struct { - Version string `yaml:"version"` - Network string `yaml:"network"` - SubNetworks []string `yaml:"subnetworks"` - DMZCIDRRange DMZCIDRRange `yaml:"dmzCIDRRange"` - SSHPublicKey string `yaml:"sshPublicKey"` - NodePools []EKSNodePool `yaml:"nodePools"` - NodePoolsLaunchKind string `yaml:"nodePoolsLaunchKind"` - Tags map[string]string `yaml:"tags"` - Auth EKSAuth `yaml:"auth"` - LogRetentionDays int `yaml:"logRetentionDays"` -} - -// EKSAuth represent a auth structure -type EKSAuth struct { - AdditionalAccounts []string `yaml:"additionalAccounts"` - Users []EKSAuthData `yaml:"users"` - Roles []EKSAuthData `yaml:"roles"` -} - -// EKSAuthData represent a auth structure -type EKSAuthData struct { - Username string `yaml:"username"` - Groups []string `yaml:"groups"` - UserARN string `yaml:"userarn,omitempty"` - RoleARN string `yaml:"rolearn,omitempty"` -} - -// EKSNodePool represent a node pool configuration -type EKSNodePool struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - MinSize int `yaml:"minSize"` - MaxSize int `yaml:"maxSize"` - InstanceType string `yaml:"instanceType"` - ContainerRuntime string `yaml:"containerRuntime"` - OS string `yaml:"os"` - TargetGroups []string `yaml:"targetGroups"` - MaxPods int `yaml:"maxPods"` - SpotInstance bool `yaml:"spotInstance"` - VolumeSize int `yaml:"volumeSize"` - Labels map[string]string `yaml:"labels"` - Taints []string `yaml:"taints"` - SubNetworks []string `yaml:"subnetworks"` - Tags map[string]string `yaml:"tags"` - AdditionalFirewallRules []EKSNodePoolFwRule `yaml:"additionalFirewallRules"` -} - -// EKSNodePoolFwRule represent an additional firewall rule to add to a specific node pool in the cluster -type EKSNodePoolFwRule struct { - Name string `yaml:"name"` - Direction string `yaml:"direction"` - CIDRBlock string `yaml:"cidrBlock"` - Protocol string `yaml:"protocol"` - Ports string `yaml:"ports"` - Tags map[string]string `yaml:"tags"` -} diff --git a/internal/cluster/creator.go b/internal/cluster/creator.go new file mode 100644 index 000000000..33811d60b --- /dev/null +++ b/internal/cluster/creator.go @@ -0,0 +1,131 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cluster + +import ( + "errors" + "fmt" + "strings" + + "github.com/sighupio/fury-distribution/pkg/config" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +const ( + CreatorPropertyConfigPath = "configpath" + CreatorPropertyWorkDir = "workdir" + CreatorPropertyFuryctlConf = "furyctlconf" + CreatorPropertyKfdManifest = "kfdmanifest" + CreatorPropertyDistroPath = "distropath" + CreatorPropertyBinPath = "binpath" + CreatorPropertyPhase = "phase" + CreatorPropertyVpnAutoConnect = "vpnautoconnect" + CreatorPropertyKubeconfig = "kubeconfig" + CreatorPropertyDryRun = "dryrun" +) + +var ( + crFactories = make(map[string]map[string]CreatorFactory) //nolint:gochecknoglobals, lll // This patterns requires crFactories + // as global to work with init function. + errResourceNotSupported = errors.New("resource is not supported") +) + +type CreatorPaths struct { + ConfigPath string + WorkDir string + DistroPath string + BinPath string + Kubeconfig string +} + +type CreatorFactory func(configPath string, props []CreatorProperty) (Creator, error) + +type CreatorProperty struct { + Name string + Value any +} + +type Creator interface { + SetProperties(props []CreatorProperty) + SetProperty(name string, value any) + Create(skipPhase string) error +} + +func NewCreator( + minimalConf config.Furyctl, + kfdManifest config.KFD, + paths CreatorPaths, + phase string, + vpnAutoConnect bool, + dryRun bool, +) (Creator, error) { + lcAPIVersion := strings.ToLower(minimalConf.APIVersion) + lcResourceType := strings.ToLower(minimalConf.Kind) + + if factoryFn, ok := crFactories[lcAPIVersion][lcResourceType]; ok { + return factoryFn(paths.ConfigPath, []CreatorProperty{ + { + Name: CreatorPropertyKfdManifest, + Value: kfdManifest, + }, + { + Name: CreatorPropertyPhase, + Value: phase, + }, + { + Name: CreatorPropertyVpnAutoConnect, + Value: vpnAutoConnect, + }, + { + Name: CreatorPropertyDistroPath, + Value: paths.DistroPath, + }, + { + Name: CreatorPropertyBinPath, + Value: paths.BinPath, + }, + { + Name: CreatorPropertyWorkDir, + Value: paths.WorkDir, + }, + { + Name: CreatorPropertyKubeconfig, + Value: paths.Kubeconfig, + }, + { + Name: CreatorPropertyDryRun, + Value: dryRun, + }, + }) + } + + return nil, fmt.Errorf("%w - type '%s' api version '%s'", errResourceNotSupported, lcResourceType, lcAPIVersion) +} + +func RegisterCreatorFactory(apiVersion, kind string, factory CreatorFactory) { + lcAPIVersion := strings.ToLower(apiVersion) + lcKind := strings.ToLower(kind) + + if _, ok := crFactories[lcAPIVersion]; !ok { + crFactories[lcAPIVersion] = make(map[string]CreatorFactory) + } + + crFactories[lcAPIVersion][lcKind] = factory +} + +func NewCreatorFactory[T Creator, S any](cc T) CreatorFactory { + return func(configPath string, props []CreatorProperty) (Creator, error) { + furyctlConf, err := yamlx.FromFileV3[S](configPath) + if err != nil { + return nil, err + } + + cc.SetProperty(CreatorPropertyConfigPath, configPath) + cc.SetProperty(CreatorPropertyFuryctlConf, furyctlConf) + cc.SetProperties(props) + + return cc, nil + } +} diff --git a/internal/cluster/deleter.go b/internal/cluster/deleter.go new file mode 100644 index 000000000..5891065d0 --- /dev/null +++ b/internal/cluster/deleter.go @@ -0,0 +1,94 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cluster + +import ( + "fmt" + "strings" + + "github.com/sighupio/fury-distribution/pkg/config" +) + +const ( + DeleterPropertyPhase = "phase" + DeleterPropertyWorkDir = "workdir" + DeleterPropertyKfdManifest = "kfdmanifest" + DeleterPropertyBinPath = "binpath" + DeleterPropertyKubeconfig = "kubeconfig" +) + +var delFactories = make(map[string]map[string]DeleterFactory) //nolint:gochecknoglobals, lll // This patterns requires factories +// as global to work with init function. + +type DeleterFactory func(props []DeleterProperty) (Deleter, error) + +type DeleterProperty struct { + Name string + Value any +} + +type Deleter interface { + SetProperties(props []DeleterProperty) + SetProperty(name string, value any) + Delete(dryRun bool) error +} + +func NewDeleter( + minimalConf config.Furyctl, + kfdManifest config.KFD, + phase, + workDir, + binPath, + kubeconfig string, +) (Deleter, error) { + lcAPIVersion := strings.ToLower(minimalConf.APIVersion) + lcResourceType := strings.ToLower(minimalConf.Kind) + + if factoryFn, ok := delFactories[lcAPIVersion][lcResourceType]; ok { + return factoryFn([]DeleterProperty{ + { + Name: DeleterPropertyKfdManifest, + Value: kfdManifest, + }, + { + Name: DeleterPropertyPhase, + Value: phase, + }, + { + Name: DeleterPropertyWorkDir, + Value: workDir, + }, + { + Name: DeleterPropertyBinPath, + Value: binPath, + }, + { + Name: DeleterPropertyKubeconfig, + Value: kubeconfig, + }, + }) + } + + return nil, fmt.Errorf("%w - type '%s' api version '%s'", errResourceNotSupported, lcResourceType, lcAPIVersion) +} + +func RegisterDeleterFactory(apiVersion, kind string, factory DeleterFactory) { + lcAPIVersion := strings.ToLower(apiVersion) + lcKind := strings.ToLower(kind) + + if _, ok := delFactories[lcAPIVersion]; !ok { + delFactories[lcAPIVersion] = make(map[string]DeleterFactory) + } + + delFactories[lcAPIVersion][lcKind] = factory +} + +func NewDeleterFactory[T Deleter](dd T) DeleterFactory { + return func(props []DeleterProperty) (Deleter, error) { + dd.SetProperties(props) + + return dd, nil + } +} diff --git a/internal/cluster/phase.go b/internal/cluster/phase.go new file mode 100644 index 000000000..c954add99 --- /dev/null +++ b/internal/cluster/phase.go @@ -0,0 +1,166 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cluster + +import ( + "errors" + "fmt" + "os" + "path" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/template" + iox "github.com/sighupio/furyctl/internal/x/io" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +const ( + OperationPhaseInfrastructure = "infrastructure" + OperationPhaseKubernetes = "kubernetes" + OperationPhaseDistribution = "distribution" + OperationPhaseAll = "" + + OperationPhaseOptionVPNAutoConnect = "vpnautoconnect" +) + +var errUnsupportedPhase = errors.New("unsupported phase, options are: infrastructure, kubernetes, distribution") + +func CheckPhase(phase string) error { + switch phase { + case OperationPhaseInfrastructure: + case OperationPhaseKubernetes: + case OperationPhaseDistribution: + case OperationPhaseAll: + { + break + } + + default: + return errUnsupportedPhase + } + + return nil +} + +type OperationPhase struct { + Path string + TerraformPath string + KustomizePath string + KubectlPath string + PlanPath string + LogsPath string + OutputsPath string + SecretsPath string + binPath string +} + +type OperationPhaseOption struct { + Name string + Value any +} + +func NewOperationPhase(folder string, kfdTools config.KFDTools, binPath string) (*OperationPhase, error) { + basePath := folder + + kustomizePath := path.Join(binPath, "kustomize", kfdTools.Common.Kustomize.Version, "kustomize") + terraformPath := path.Join(binPath, "terraform", kfdTools.Common.Terraform.Version, "terraform") + kubectlPath := path.Join(binPath, "kubectl", kfdTools.Common.Kubectl.Version, "kubectl") + + planPath := path.Join(basePath, "terraform", "plan") + logsPath := path.Join(basePath, "terraform", "logs") + outputsPath := path.Join(basePath, "terraform", "outputs") + secretsPath := path.Join(basePath, "terraform", "secrets") + + return &OperationPhase{ + Path: basePath, + TerraformPath: terraformPath, + KustomizePath: kustomizePath, + KubectlPath: kubectlPath, + PlanPath: planPath, + LogsPath: logsPath, + OutputsPath: outputsPath, + SecretsPath: secretsPath, + binPath: binPath, + }, nil +} + +func (cp *OperationPhase) CreateFolder() error { + if _, err := os.Stat(cp.Path); !os.IsNotExist(err) { + return nil + } + + err := os.Mkdir(cp.Path, iox.FullPermAccess) + if err != nil { + return fmt.Errorf("error creating folder %s: %w", cp.Path, err) + } + + return nil +} + +func (cp *OperationPhase) CreateFolderStructure() error { + if _, err := os.Stat(cp.PlanPath); os.IsNotExist(err) { + if err := os.Mkdir(cp.PlanPath, iox.FullPermAccess); err != nil { + return fmt.Errorf("error creating folder %s: %w", cp.PlanPath, err) + } + } + + if _, err := os.Stat(cp.LogsPath); os.IsNotExist(err) { + if err := os.Mkdir(cp.LogsPath, iox.FullPermAccess); err != nil { + return fmt.Errorf("error creating folder %s: %w", cp.LogsPath, err) + } + } + + if _, err := os.Stat(cp.SecretsPath); os.IsNotExist(err) { + if err := os.Mkdir(cp.SecretsPath, iox.FullPermAccess); err != nil { + return fmt.Errorf("error creating folder %s: %w", cp.SecretsPath, err) + } + } + + if _, err := os.Stat(cp.OutputsPath); os.IsNotExist(err) { + if err := os.Mkdir(cp.OutputsPath, iox.FullPermAccess); err != nil { + return fmt.Errorf("error creating folder %s: %w", cp.OutputsPath, err) + } + } + + return nil +} + +func (*OperationPhase) CopyFromTemplate(cfg template.Config, prefix, sourcePath, targetPath string) error { + outDirPath, err := os.MkdirTemp("", fmt.Sprintf("furyctl-%s-", prefix)) + if err != nil { + return fmt.Errorf("error creating temp folder: %w", err) + } + + tfConfigPath := path.Join(outDirPath, "tf-config.yaml") + + tfConfig, err := yamlx.MarshalV2(cfg) + if err != nil { + return fmt.Errorf("error marshaling tf-config: %w", err) + } + + if err = os.WriteFile(tfConfigPath, tfConfig, iox.RWPermAccess); err != nil { + return fmt.Errorf("error writing tf-config: %w", err) + } + + templateModel, err := template.NewTemplateModel( + sourcePath, + targetPath, + tfConfigPath, + outDirPath, + ".tpl", + false, + false, + ) + if err != nil { + return fmt.Errorf("error creating template model: %w", err) + } + + err = templateModel.Generate() + if err != nil { + return fmt.Errorf("error generating from template: %w", err) + } + + return nil +} diff --git a/internal/cluster/provisioners/eks/provisioner.go b/internal/cluster/provisioners/eks/provisioner.go deleted file mode 100644 index 1532a372d..000000000 --- a/internal/cluster/provisioners/eks/provisioner.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package eks - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "strings" - - "github.com/gobuffalo/packr/v2" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - cfg "github.com/sighupio/furyctl/internal/cluster/configuration" - "github.com/sighupio/furyctl/internal/configuration" -) - -// InitMessage return a custom provisioner message the user will see once the cluster is ready to be updated -func (e *EKS) InitMessage() string { - return `[EKS] Fury - -This provisioner creates a battle-tested AWS EKS Kubernetes Cluster -with a private and production-grade setup. - -Requires to connect to a VPN server to deploy the cluster from this computer. -Use a bastion host (inside the EKS VPC) as an alternative method to deploy the cluster. - -The provisioner requires the following software installed: -- /bin/sh -- wget or curl -- aws-cli -- kubectl -` -} - -// UpdateMessage return a custom provisioner message the user will see once the cluster is updated -func (e *EKS) UpdateMessage() string { - var output map[string]tfexec.OutputMeta - output, err := e.terraform.Output(context.Background()) - if err != nil { - logrus.Error("Can not get output values") - } - var clusterEndpoint, clusterOperatorName string - err = json.Unmarshal(output["cluster_endpoint"].Value, &clusterEndpoint) - if err != nil { - logrus.Error("Can not get `cluster_endpoint` value") - } - err = json.Unmarshal(output["operator_ssh_user"].Value, &clusterOperatorName) - if err != nil { - logrus.Error("Can not get `operator_ssh_user` value") - } - return fmt.Sprintf( - `[EKS] Fury - -All the cluster components are up to date. -EKS Kubernetes cluster ready. - -EKS Cluster Endpoint: %v -SSH Operator Name: %v - -Use the ssh %v username to access the EKS instances with the configured SSH key. -Discover the instances by running - -$ kubectl get nodes - -Then access by running: - -$ ssh %v@node-name-reported-by-kubectl-get-nodes - -`, clusterEndpoint, clusterOperatorName, clusterOperatorName, clusterOperatorName, - ) -} - -// DestroyMessage return a custom provisioner message the user will see once the cluster is destroyed -func (e *EKS) DestroyMessage() string { - return `[EKS] Fury -All cluster components were destroyed. -EKS control plane and workers went away. - -Had problems, contact us at sales@sighup.io. -` -} - -// Enterprise return a boolean indicating it is an enterprise provisioner -func (e *EKS) Enterprise() bool { - return false -} - -// EKS represents the EKS provisioner -type EKS struct { - terraform *tfexec.Terraform - box *packr.Box - config *configuration.Configuration -} - -const ( - projectPath = "../../../../data/provisioners/cluster/eks" -) - -func (e EKS) createVarFile() (err error) { - var buffer bytes.Buffer - spec := e.config.Spec.(cfg.EKS) - buffer.WriteString(fmt.Sprintf("cluster_name = \"%v\"\n", e.config.Metadata.Name)) - buffer.WriteString(fmt.Sprintf("cluster_version = \"%v\"\n", spec.Version)) - if spec.LogRetentionDays != 0 { - buffer.WriteString(fmt.Sprintf("cluster_log_retention_in_days = %v\n", spec.LogRetentionDays)) - } - buffer.WriteString(fmt.Sprintf("network = \"%v\"\n", spec.Network)) - buffer.WriteString(fmt.Sprintf("subnetworks = [\"%v\"]\n", strings.Join(spec.SubNetworks, "\",\""))) - buffer.WriteString(fmt.Sprintf("dmz_cidr_range = [\"%v\"]\n", strings.Join(spec.DMZCIDRRange.Values, "\",\""))) - buffer.WriteString(fmt.Sprintf("ssh_public_key = \"%v\"\n", spec.SSHPublicKey)) - if len(spec.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(spec.Tags) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) - } - - if len(spec.Auth.AdditionalAccounts) > 0 { - buffer.WriteString( - fmt.Sprintf( - "eks_map_accounts = [\"%v\"]\n", - strings.Join(spec.Auth.AdditionalAccounts, "\",\""), - ), - ) - } - - if len(spec.Auth.Users) > 0 { - buffer.WriteString("eks_map_users = [\n") - for _, account := range spec.Auth.Users { - buffer.WriteString( - fmt.Sprintf( - `{ - groups = ["%v"] - username = "%v" - userarn = "%v" -}, -`, strings.Join(account.Groups, "\",\""), account.Username, account.UserARN, - ), - ) - } - buffer.WriteString("]\n") - - } - - if len(spec.Auth.Roles) > 0 { - buffer.WriteString("eks_map_roles = [\n") - for _, account := range spec.Auth.Roles { - buffer.WriteString( - fmt.Sprintf( - `{ - groups = ["%v"] - username = "%v" - rolearn = "%v" -}, -`, strings.Join(account.Groups, "\",\""), account.Username, account.RoleARN, - ), - ) - } - buffer.WriteString("]\n") - } - - if len(spec.NodePools) > 0 { - buffer.WriteString("node_pools = [\n") - for _, np := range spec.NodePools { - buffer.WriteString("{\n") - buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", np.Name)) - if np.Version != "" { - buffer.WriteString(fmt.Sprintf("version = \"%v\"\n", np.Version)) - } else { - buffer.WriteString("version = null\n") - } - if np.ContainerRuntime != "" { - buffer.WriteString(fmt.Sprintf("container_runtime = \"%v\"\n", np.ContainerRuntime)) - } - buffer.WriteString(fmt.Sprintf("spot_instance = %v\n", np.SpotInstance)) - buffer.WriteString(fmt.Sprintf("min_size = %v\n", np.MinSize)) - buffer.WriteString(fmt.Sprintf("max_size = %v\n", np.MaxSize)) - buffer.WriteString(fmt.Sprintf("instance_type = \"%v\"\n", np.InstanceType)) - - if np.OS != "" { - buffer.WriteString(fmt.Sprintf("os = \"%v\"\n", np.OS)) - } - if len(np.TargetGroups) > 0 { - buffer.WriteString(fmt.Sprintf("eks_target_group_arns = [\"%v\"]\n", strings.Join(np.TargetGroups, "\",\""))) - } - - if np.MaxPods > 0 { - buffer.WriteString(fmt.Sprintf("max_pods = %v\n", np.MaxPods)) - } - buffer.WriteString(fmt.Sprintf("volume_size = %v\n", np.VolumeSize)) - - if len(np.AdditionalFirewallRules) > 0 { - - buffer.WriteString("additional_firewall_rules = [\n") - for _, fwRule := range np.AdditionalFirewallRules { - - fwRuleTags := "{}" - if len(fwRule.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(fwRule.Tags) - if err != nil { - return err - } - fwRuleTags = string(tags) - } - - buffer.WriteString( - fmt.Sprintf( - `{ - name = "%v" - direction = "%v" - cidr_block = "%v" - protocol = "%v" - ports = "%v" - tags = %v - }, - `, fwRule.Name, fwRule.Direction, fwRule.CIDRBlock, fwRule.Protocol, fwRule.Ports, fwRuleTags, - ), - ) - } - buffer.WriteString("]\n") - } else { - buffer.WriteString("additional_firewall_rules = []\n") - } - - if len(np.SubNetworks) > 0 { - buffer.WriteString(fmt.Sprintf("subnetworks = [\"%v\"]\n", strings.Join(np.SubNetworks, "\",\""))) - } else { - buffer.WriteString("subnetworks = null\n") - } - if len(np.Labels) > 0 { - var labels []byte - labels, err = json.Marshal(np.Labels) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("labels = %v\n", string(labels))) - } else { - buffer.WriteString("labels = {}\n") - } - - if len(np.Taints) > 0 { - buffer.WriteString(fmt.Sprintf("taints = [\"%v\"]\n", strings.Join(np.Taints, "\",\""))) - } else { - buffer.WriteString("taints = []\n") - } - - if len(np.Tags) > 0 { - var tags []byte - tags, err = json.Marshal(np.Tags) - if err != nil { - return err - } - buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) - } else { - buffer.WriteString("tags = {}\n") - } - - buffer.WriteString("},\n") - } - buffer.WriteString("]\n") - } - err = ioutil.WriteFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir()), buffer.Bytes(), 0o600) - if err != nil { - return err - } - err = e.terraform.FormatWrite( - context.Background(), - tfexec.Dir(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - return err - } - return nil -} - -// New instantiates a new EKS provisioner -func New(config *configuration.Configuration) *EKS { - b := packr.New("ekscluster", projectPath) - return &EKS{ - box: b, - config: config, - } -} - -// SetTerraformExecutor adds the terraform executor to this provisioner -func (e *EKS) SetTerraformExecutor(tf *tfexec.Terraform) { - e.terraform = tf -} - -// TerraformExecutor returns the current terraform executor of this provisioner -func (e *EKS) TerraformExecutor() (tf *tfexec.Terraform) { - return e.terraform -} - -// Box returns the box that has the files as binary data -func (e EKS) Box() *packr.Box { - return e.box -} - -// TerraformFiles returns the list of files conforming the terraform project -func (e EKS) TerraformFiles() []string { - // TODO understand if it is possible to deduce these values somehow - // find . -type f -follow -print - return []string{ - "output.tf", - "main.tf", - "variables.tf", - } -} - -func (e EKS) Prepare() (err error) { - return nil -} - -// Plan runs a dry run execution -func (e EKS) Plan() (err error) { - logrus.Info("[DRYRUN] Updating EKS Cluster project") - err = e.createVarFile() - if err != nil { - return err - } - var changes bool - changes, err = e.terraform.Plan( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("[DRYRUN] Something went wrong while updating eks. %v", err) - return err - } - if changes { - logrus.Warn("[DRYRUN] Something changed along the time. Remove dryrun option to apply the desired state") - } else { - logrus.Info("[DRYRUN] Everything is up to date") - } - - logrus.Info("[DRYRUN] EKS Updated") - return nil -} - -// Update runs terraform apply in the project -func (e EKS) Update() (string, error) { - logrus.Info("Updating EKS project") - err := e.createVarFile() - if err != nil { - return "", err - } - err = e.terraform.Apply( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("Something went wrong while updating eks. %v", err) - return "", err - } - - logrus.Info("EKS Updated") - return e.kubeconfig() -} - -// Destroy runs terraform destroy in the project -func (e EKS) Destroy() (err error) { - logrus.Info("Destroying EKS project") - err = e.createVarFile() - if err != nil { - return err - } - err = e.terraform.Destroy( - context.Background(), - tfexec.VarFile(fmt.Sprintf("%v/eks.tfvars", e.terraform.WorkingDir())), - ) - if err != nil { - logrus.Fatalf("Something went wrong while destroying EKS cluster project. %v", err) - return err - } - logrus.Info("EKS destroyed") - return nil -} - -func (e EKS) kubeconfig() (string, error) { - logrus.Info("Gathering output file as json") - var output map[string]tfexec.OutputMeta - output, err := e.terraform.Output(context.Background()) - if err != nil { - logrus.Fatalf("Error while getting project output: %v", err) - return "", err - } - var creds string - err = json.Unmarshal(output["kubeconfig"].Value, &creds) - if err != nil { - logrus.Fatalf("Error while tranforming the kubeconfig value into string: %v", err) - return "", err - } - return creds, err -} diff --git a/internal/cmd/cmdutil/flag.go b/internal/cmd/cmdutil/flag.go new file mode 100644 index 000000000..6591f8a28 --- /dev/null +++ b/internal/cmd/cmdutil/flag.go @@ -0,0 +1,49 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmdutil + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/analytics" +) + +var ErrParsingFlag = errors.New("error while parsing flag") + +func BoolFlag(cmd *cobra.Command, flagName string, tracker *analytics.Tracker, event analytics.Event) (bool, error) { + value, err := cmd.Flags().GetBool(flagName) + if err != nil { + event.AddErrorMessage(fmt.Errorf("%w: %s", ErrParsingFlag, flagName)) + tracker.Track(event) + + return false, fmt.Errorf("%w: %s", ErrParsingFlag, flagName) + } + + return value, nil +} + +func StringFlag(cmd *cobra.Command, flagName string, tracker *analytics.Tracker, event analytics.Event) (string, error) { + value, err := cmd.Flags().GetString(flagName) + if err != nil { + event.AddErrorMessage(fmt.Errorf("%w: %s", ErrParsingFlag, flagName)) + tracker.Track(event) + + return "", fmt.Errorf("%w: %s", ErrParsingFlag, flagName) + } + + return value, nil +} + +func StringFlagOptional(cmd *cobra.Command, flagName string) string { + value, err := cmd.Flags().GetString(flagName) + if err != nil { + return "" + } + + return value +} diff --git a/internal/config/validate.go b/internal/config/validate.go new file mode 100644 index 000000000..37bb0ea03 --- /dev/null +++ b/internal/config/validate.go @@ -0,0 +1,138 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package config + +import ( + "fmt" + "html/template" + "os" + "path/filepath" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/schema/santhosh" + iox "github.com/sighupio/furyctl/internal/x/io" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +var ErrConfigCreationFailed = fmt.Errorf("config creation failed") + +func Create( + res distribution.DownloadResult, + furyctlPath string, + cmdEvent analytics.Event, + tracker *analytics.Tracker, + data map[string]string, +) (*os.File, error) { + tplPath, err := distribution.GetConfigTemplatePath(res.RepoPath, res.MinimalConf) + if err != nil { + return nil, fmt.Errorf("error getting schema path: %w", err) + } + + tplContent, err := os.ReadFile(tplPath) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return nil, fmt.Errorf("error reading furyctl yaml template: %w", err) + } + + tmpl, err := template.New("furyctl.yaml").Parse(string(tplContent)) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return nil, fmt.Errorf("error parsing furyctl yaml template: %w", err) + } + + out, err := createNewEmptyConfigFile(furyctlPath) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return nil, err + } + + if err := tmpl.Execute(out, data); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return nil, fmt.Errorf("error executing furyctl yaml template: %w", err) + } + + return out, nil +} + +func createNewEmptyConfigFile(path string) (*os.File, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf("error getting absolute path: %w", err) + } + + if _, err := os.Stat(absPath); err == nil { + p := filepath.Dir(absPath) + + return nil, fmt.Errorf( + "%w: a furyctl.yaml configuration file already exists in %s, please remove it and try again", + ErrConfigCreationFailed, + p, + ) + } + + if err := os.MkdirAll(filepath.Dir(absPath), iox.FullPermAccess); err != nil { + return nil, fmt.Errorf("error creating directory: %w", err) + } + + out, err := os.Create(absPath) + if err != nil { + return nil, fmt.Errorf("error creating file: %w", err) + } + + return out, nil +} + +// Validate the furyctl.yaml file. +func Validate(path, repoPath string) error { + miniConf, err := loadFromFile(path) + if err != nil { + return err + } + + schemaPath, err := distribution.GetSchemaPath(repoPath, miniConf) + if err != nil { + return fmt.Errorf("error getting schema path: %w", err) + } + + schema, err := santhosh.LoadSchema(schemaPath) + if err != nil { + return fmt.Errorf("error loading schema: %w", err) + } + + conf, err := yamlx.FromFileV3[any](path) + if err != nil { + return err + } + + err = schema.Validate(conf) + if err != nil { + return fmt.Errorf("error while validating: %w", err) + } + + return nil +} + +func loadFromFile(path string) (config.Furyctl, error) { + conf, err := yamlx.FromFileV3[config.Furyctl](path) + if err != nil { + return config.Furyctl{}, err + } + + if err := config.NewValidator().Struct(conf); err != nil { + return config.Furyctl{}, fmt.Errorf("%w: %v", distribution.ErrValidateConfig, err) + } + + return conf, err +} diff --git a/internal/configuration/assets/aws-bootstrap-file-state.yml b/internal/configuration/assets/aws-bootstrap-file-state.yml deleted file mode 100644 index a50711009..000000000 --- a/internal/configuration/assets/aws-bootstrap-file-state.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: my-aws-poc -executor: - state: - backend: local - config: - path: "mystate.tfstate" -provisioner: aws -spec: - networkCIDR: "10.0.0.0/16" - publicSubnetsCIDRs: - - "10.0.1.0/24" - - "10.0.2.0/24" - - "10.0.3.0/24" - privateSubnetsCIDRs: - - "10.0.101.0/24" - - "10.0.102.0/24" - - "10.0.103.0/24" - vpn: - instanceType: t3.large # Default - port: 1194 # Default - diskSize: 50 # Default - dhParamsBits: 2048 # Default - subnetCIDR: 192.168.100.0/24 - sshUsers: - - angelbarrera92 - operatorName: sighup # Default - operatorCIDRs: - - 1.2.3.4/32 # Default diff --git a/internal/configuration/assets/aws-bootstrap.yml b/internal/configuration/assets/aws-bootstrap.yml deleted file mode 100644 index 6101d688e..000000000 --- a/internal/configuration/assets/aws-bootstrap.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: my-aws-poc -provisioner: aws -spec: - networkCIDR: "10.0.0.0/16" - publicSubnetsCIDRs: - - "10.0.1.0/24" - - "10.0.2.0/24" - - "10.0.3.0/24" - privateSubnetsCIDRs: - - "10.0.101.0/24" - - "10.0.102.0/24" - - "10.0.103.0/24" - vpn: - instanceType: t3.large # Default - port: 1194 # Default - diskSize: 50 # Default - dhParamsBits: 2048 # Default - subnetCIDR: 192.168.100.0/24 - sshUsers: - - angelbarrera92 - operatorName: sighup # Default - operatorCIDRs: - - 1.2.3.4/32 # Default diff --git a/internal/configuration/assets/eks-cluster.yml b/internal/configuration/assets/eks-cluster.yml deleted file mode 100644 index 92301811e..000000000 --- a/internal/configuration/assets/eks-cluster.yml +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: demo -executor: - version: 0.12.29 - state: - backend: s3 - config: - bucket: "terraform-e2e-fury-testing-angel" - key: "cli/demo/cluster" - region: "eu-central-1" -provisioner: eks -spec: - version: "1.23" - logRetentionDays: 30 - network: "vpc-1" - subnetworks: - - "subnet-1" - - "subnet-2" - - "subnet-3" - dmzCIDRRange: "0.0.0.0/0" - sshPublicKey: "123" - nodePoolsLaunchKind: "launch_template" - nodePools: - - name: "one" - version: "1.23" - minSize: 0 - maxSize: 10 - instanceType: "m" - maxPods: 100 - os: "centos" - targetGroups: - - "t1" - - "t2" - volumeSize: 50 - labels: - hello: World - taints: - - "hello" - tags: - hello: World diff --git a/internal/configuration/assets/invalid-config.yml b/internal/configuration/assets/invalid-config.yml deleted file mode 100644 index 7373732eb..000000000 --- a/internal/configuration/assets/invalid-config.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Cluster -metadata: - name: my-cluster -provisioner: undefined -spec: {} diff --git a/internal/configuration/assets/invalid-kind.yml b/internal/configuration/assets/invalid-kind.yml deleted file mode 100644 index c9c43be7a..000000000 --- a/internal/configuration/assets/invalid-kind.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Pizza -metadata: - name: my-pizza -provisioner: undefined -spec: {} diff --git a/internal/configuration/assets/invalid-provisioner-bootstrap.yml b/internal/configuration/assets/invalid-provisioner-bootstrap.yml deleted file mode 100644 index 9535c9b7d..000000000 --- a/internal/configuration/assets/invalid-provisioner-bootstrap.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -kind: Bootstrap -metadata: - name: my-base -provisioner: undefined -spec: {} diff --git a/internal/configuration/config.go b/internal/configuration/config.go deleted file mode 100644 index 2c7bec173..000000000 --- a/internal/configuration/config.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -import ( - "errors" - "fmt" - "io/ioutil" - - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" - - bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" -) - -// TerraformExecutor represents the terraform executor configuration to be used -type TerraformExecutor struct { - // StateConfiguration configures the terraform state to use - StateConfiguration StateConfiguration `yaml:"state"` -} - -// StateConfiguration represents the terraform state configuration to be used -type StateConfiguration struct { - Backend string `yaml:"backend"` - Config map[string]string `yaml:"config"` -} - -// Configuration represents the base of the configuration file -type Configuration struct { - Kind string `yaml:"kind"` - Metadata Metadata `yaml:"metadata"` - Spec interface{} `yaml:"spec"` - Executor TerraformExecutor `yaml:"executor"` - Provisioner string `yaml:"provisioner"` -} - -// Metadata represents a set of metadata information to be used while performing operations -type Metadata struct { - Name string `yaml:"name"` - Labels map[string]interface{} `yaml:"labels"` -} - -// Parse parses a yaml configuration file (path) returning the parsed configuration file as a Configuration struct -func Parse(path string) (*Configuration, error) { - content, err := ioutil.ReadFile(path) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return nil, err - } - baseConfig := &Configuration{} - err = yaml.Unmarshal(content, &baseConfig) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return nil, err - } - - switch { - case baseConfig.Kind == "Cluster": - err = clusterParser(baseConfig) - if err != nil { - return nil, err - } - return baseConfig, nil - case baseConfig.Kind == "Bootstrap": - err = bootstrapParser(baseConfig) - if err != nil { - return nil, err - } - return baseConfig, nil - default: - logrus.Errorf("Error parsing the configuration file. Parser not found for %v kind", baseConfig.Kind) - return nil, fmt.Errorf("parser not found for %v kind", baseConfig.Kind) - } -} - -func clusterParser(config *Configuration) (err error) { - provisioner := config.Provisioner - logrus.Debugf("provisioner: %v", provisioner) - specBytes, err := yaml.Marshal(config.Spec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - switch { - case provisioner == "eks": - eksSpec := clustercfg.EKS{} - err = yaml.Unmarshal(specBytes, &eksSpec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - config.Spec = eksSpec - return nil - default: - logrus.Error("Error parsing the configuration file. Provisioner not found") - return errors.New("cluster provisioner not found") - } -} - -func bootstrapParser(config *Configuration) (err error) { - provisioner := config.Provisioner - logrus.Debugf("provisioner: %v", provisioner) - specBytes, err := yaml.Marshal(config.Spec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - switch { - case provisioner == "aws": - awsSpec := bootstrapcfg.AWS{ - VPN: bootstrapcfg.AWSVPN{ - Instances: 1, - }, - } - err = yaml.Unmarshal(specBytes, &awsSpec) - if err != nil { - logrus.Errorf("error parsing configuration file: %v", err) - return err - } - config.Spec = awsSpec - return nil - default: - logrus.Error("Error parsing the configuration file. Provisioner not found") - return errors.New("bootstrap provisioner not found") - } -} diff --git a/internal/configuration/config_test.go b/internal/configuration/config_test.go deleted file mode 100644 index c8033074d..000000000 --- a/internal/configuration/config_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package configuration - -import ( - "reflect" - "testing" - - bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" -) - -var ( - sampleEKSConfig Configuration - sampleAWSBootstrap Configuration -) - -func init() { - sampleAWSBootstrap.Kind = "Bootstrap" - sampleAWSBootstrap.Metadata = Metadata{ - Name: "my-aws-poc", - } - sampleAWSBootstrap.Provisioner = "aws" - sampleAWSBootstrap.Spec = bootstrapcfg.AWS{ - NetworkCIDR: "10.0.0.0/16", - PublicSubnetsCIDRs: []string{"10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"}, - PrivateSubnetsCIDRs: []string{"10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"}, - VPN: bootstrapcfg.AWSVPN{ - Instances: 1, - InstanceType: "t3.large", - Port: 1194, - DiskSize: 50, - DHParamsBits: 2048, - SubnetCIDR: "192.168.100.0/24", - SSHUsers: []string{"angelbarrera92"}, - OperatorName: "sighup", - OperatorCIDRs: []string{"1.2.3.4/32"}, - }, - } - - sampleEKSConfig.Kind = "Cluster" - sampleEKSConfig.Metadata = Metadata{ - Name: "demo", - } - sampleEKSConfig.Executor.StateConfiguration = StateConfiguration{ - Backend: "s3", - Config: map[string]string{ - "bucket": "terraform-e2e-fury-testing-angel", - "key": "cli/demo/cluster", - "region": "eu-central-1", - }, - } - sampleEKSConfig.Provisioner = "eks" - sampleEKSConfig.Spec = clustercfg.EKS{ - Version: "1.23", - Network: "vpc-1", - SubNetworks: []string{"subnet-1", "subnet-2", "subnet-3"}, - DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"0.0.0.0/0"}}, - SSHPublicKey: "123", - LogRetentionDays: 30, - NodePools: []clustercfg.EKSNodePool{ - { - Name: "one", - Version: "1.23", - MinSize: 0, - MaxSize: 10, - InstanceType: "m", - MaxPods: 100, - OS: "centos", - TargetGroups: []string{"t1", "t2"}, - VolumeSize: 50, - Labels: map[string]string{ - "hello": "World", - }, - Taints: []string{"hello"}, - Tags: map[string]string{ - "hello": "World", - }, - }, - }, - } -} - -func TestParseClusterConfigurationFile(t *testing.T) { - sampleAWSBootstrapLocalState := sampleAWSBootstrap - sampleAWSBootstrapLocalState.Executor.StateConfiguration.Backend = "local" - sampleAWSBootstrapLocalState.Executor.StateConfiguration.Config = map[string]string{ - "path": "mystate.tfstate", - } - - type args struct { - path string - } - tests := []struct { - name string - args args - want *Configuration - wantErr bool - }{ - { - name: "AWS config", - args: args{ - path: "assets/aws-bootstrap.yml", - }, - want: &sampleAWSBootstrap, - wantErr: false, - }, - { - name: "AWS config - File State", - args: args{ - path: "assets/aws-bootstrap-file-state.yml", - }, - want: &sampleAWSBootstrapLocalState, - wantErr: false, - }, - { - name: "EKS config", - args: args{ - path: "assets/eks-cluster.yml", - }, - want: &sampleEKSConfig, - wantErr: false, - }, - { - name: "Undefined provisioner", - args: args{ - path: "assets/invalid-config.yml", - }, - want: nil, - wantErr: true, - }, - { - name: "Undefined provisioner for bootstrap", - args: args{ - path: "assets/invalid-provisioner-bootstrap.yml", - }, - want: nil, - wantErr: true, - }, - { - name: "Invalid kind", - args: args{ - path: "assets/invalid-kind.yml", - }, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Parse(tt.args.path) - if (err != nil) != tt.wantErr { - t.Errorf("ParseClusterConfigurationFile() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseClusterConfigurationFile() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/configuration/templates.go b/internal/configuration/templates.go deleted file mode 100644 index 4d0db9c06..000000000 --- a/internal/configuration/templates.go +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package configuration TODO -package configuration - -import ( - "fmt" - - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" - - bootstrapcfg "github.com/sighupio/furyctl/internal/bootstrap/configuration" - clustercfg "github.com/sighupio/furyctl/internal/cluster/configuration" -) - -// Template generates a yaml with a sample configuration requested by the client -func Template(kind, provisioner string) (string, error) { - config := Configuration{} - config.Kind = kind - config.Provisioner = provisioner - switch { - case kind == "Bootstrap": - err := bootstrapTemplate(&config) - if err != nil { - return "", err - } - case kind == "Cluster": - err := clusterTemplate(&config) - if err != nil { - return "", err - } - default: - logrus.Errorf("Error creating a template configuration file. Parser not found for %v kind", kind) - return "", fmt.Errorf("error creating a template configuration file. Parser not found for %v kind", kind) - } - b, err := yaml.Marshal(config) - if err != nil { - return "", err - } - return string(b), nil -} - -func createBase(config *Configuration) { - config.Metadata = Metadata{ - Name: "nameOfTheProject # Used to identify your resources", - Labels: map[string]interface{}{ - "yourLabel": "yourValue # You Can add as much labels as you required. You can add bool, string, number values", - "boolLabel": true, - }, - } - config.Executor = TerraformExecutor{ - StateConfiguration: StateConfiguration{ - Backend: "local # Specify your backend configuration. local is the default one. https://www.terraform.io/docs/configuration/backend.html", - Config: map[string]string{ - "path": "workdir/terraform.state # Set the backend configuration parameter. this path attribute works with the local backend.", - }, - }, - } -} - -func bootstrapTemplate(config *Configuration) error { - switch { - case config.Provisioner == "aws": - spec := bootstrapcfg.AWS{ - NetworkCIDR: "10.0.0.0/16 # Required. Specific VPC Network CIDR to create", - PublicSubnetsCIDRs: []string{"Required", "10.0.10.0/24", "10.0.20.0/24", "10.0.30.0/24"}, - PrivateSubnetsCIDRs: []string{"Required", "10.0.192.0/24", "10.0.182.0/24", "10.0.172.0/24"}, - VPN: bootstrapcfg.AWSVPN{ - Instances: 2, - Port: 1194, - InstanceType: "t3.micro # This is the default value. Specify any AWS EC2 instance type", - DiskSize: 50, - OperatorName: "sighup # This is the default value. SSH User to access the instance", - DHParamsBits: 2048, - SubnetCIDR: "172.16.0.0/16 # Required attribute. Should be different from the networkCIDR", - SSHUsers: []string{"Required", "angelbarrera92", "jnardiello"}, - OperatorCIDRs: []string{"0.0.0.0/0", "This is the default value"}, - }, - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate all resources. Optional", - }, - } - config.Spec = spec - default: - log.Errorf( - "Error creating a template configuration file. Parser not found for %v provisioner", - config.Provisioner, - ) - return fmt.Errorf( - "error creating a template configuration file. Parser not found for %v provisioner", - config.Provisioner, - ) - } - createBase(config) - return nil -} - -func clusterTemplate(config *Configuration) error { - switch { - case config.Provisioner == "eks": - spec := clustercfg.EKS{ - Version: "1.20 # EKS Control plane version", - Network: "vpc-id1 # Identificator of the VPC", - LogRetentionDays: 30, - SubNetworks: []string{"subnet-id1 # Identificator of the subnets"}, - DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"10.0.0.0/8", "Required. Network CIDR range from where cluster control plane will be accessible"}}, - SSHPublicKey: "sha-rsa 190jd0132w. Required. Cluster administrator public ssh key. Used to access cluster nodes.", - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate all resources. Optional", - }, - Auth: clustercfg.EKSAuth{ - AdditionalAccounts: []string{"777777777777", "88888888888", "# Additional AWS account numbers to add to the aws-auth configmap"}, - Users: []clustercfg.EKSAuthData{ - { - Username: "user1 # Any username", - Groups: []string{"system:masters", "# Any K8S Group"}, - UserARN: "arn:user:7777777... # The user ARN", - }, - }, - Roles: []clustercfg.EKSAuthData{ - { - Username: "user1 # Any username", - Groups: []string{"system:masters", "# Any K8S Group"}, - RoleARN: "arn:role:7777777... # The role ARN", - }, - }, - }, - NodePoolsLaunchKind: "# either `launch_configurations`, `launch_templates` or `both`. For new clusters use `launch_templates`, for existing cluster you'll need to migrate from `launch_configurations` to `launch_templates` using `both` as interim.", - NodePools: []clustercfg.EKSNodePool{ - { - Name: "my-node-pool. Required. Name of the node pool", - Version: "1.23. Required. null to use Control Plane version.", - MinSize: 0, - MaxSize: 1, - InstanceType: "t3.micro. Required. AWS EC2 isntance types", - MaxPods: 110, - VolumeSize: 50, - SubNetworks: []string{"subnet-1", "# any subnet id where you want your nodes running"}, - Labels: map[string]string{ - "environment": "dev. # Node labels. Use it to tag nodes then use it on Kubernetes", - }, - OS: "ami-12345 # The ami-id to use. Do not specify to use the default one", - TargetGroups: []string{"target-12345", "the target-id to associate the instance to a loadbalancer"}, - Taints: []string{"key1=value1:NoSchedule. As an example"}, - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate nodepool resources. Optional", - }, - AdditionalFirewallRules: []clustercfg.EKSNodePoolFwRule{ - { - Name: "The name of rule # Identify the rule using a description", - Direction: "ingress|egress # Choose one", - CIDRBlock: "0.0.0.0/0 # CIDR Block", - Protocol: "TCP|UDP # Any supported AWS security group protocol", - Ports: "80-80 # Port range. This one means from 80 to 80", - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate nodepool resources. Optional", - }, - }, - }, - }, - }, - } - config.Spec = spec - case config.Provisioner == "gke": - spec := clustercfg.GKE{ - Version: "1.23.12-gke.1206 # GKE Control plane version", - Network: "vpc-id1 # Identificator of the Network", - NetworkProjectID: "12309123 # OPTIONAL. The project ID of the shared VPC's host (for shared vpc support)", - ControlPlaneCIDR: "10.0.0.0/28 # OPTIONAL. DEFAULT VALUE. The IP range in CIDR notation to use for the hosted master network", - AdditionalFirewallRules: true, - AdditionalClusterFirewallRules: false, - DisableDefaultSNAT: false, - SubNetworks: []string{ - "subnet-id0 # Identificator of the subnets. Index 0: Cluster Subnet", - "subnet-id1 # Identificator of the subnets. Index 1: Pod Subnet", - "subnet-id2 # Identificator of the subnets. Index 1: Service Subnet", - }, - - DMZCIDRRange: clustercfg.DMZCIDRRange{Values: []string{"10.0.0.0/8", "Required. Network CIDR range from where cluster control plane will be accessible"}}, - SSHPublicKey: "sha-rsa 190jd0132w. Required. Cluster administrator public ssh key. Used to access cluster nodes.", - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate all resources. Optional", - }, - - NodePools: []clustercfg.GKENodePool{ - { - Name: "my-node-pool. Required. Name of the node pool", - Version: "1.23.12-gke.1206. Required. null to use Control Plane version.", - MinSize: 1, - MaxSize: 1, - InstanceType: "n1-standard-1. Required. GCP instance types", - OS: "COS # The operating system to use. Do not specify to use the default one (COS)", - MaxPods: 110, - VolumeSize: 50, - SubNetworks: []string{"subnet-1", "# availability zones (example: us-central1-a) where to place the nodes. Useful to don't create them on all zones"}, - Labels: map[string]string{ - "environment": "dev. # Node labels. Use it to tag nodes then use it on Kubernetes", - }, - Taints: []string{"key1=value1:NoSchedule. As an example"}, - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate nodepool resources. Optional", - }, - AdditionalFirewallRules: []clustercfg.GKENodePoolFwRule{ - { - Name: "The name of rule # Identify the rule using a description", - Direction: "ingress|egress # Choose one", - CIDRBlock: "0.0.0.0/0 # CIDR Block", - Protocol: "TCP|UDP # Any supported GCP security group protocol", - Ports: "80-80 # Port range. This one means from 80 to 80", - Tags: map[string]string{ - "myTag": "MyValue # Use this tags to annotate nodepool resources. Optional", - }, - }, - }, - }, - }, - } - config.Spec = spec - case config.Provisioner == "vsphere": - spec := clustercfg.VSphere{ - Version: "1.20.5 # Place here the Kubernetes version you want to use", - ControlPlaneEndpoint: "my-cluster.localdomain # OPTIONAL. Kubernetes control plane endpoint. Default to the VIP of the load balancer", - ETCDConfig: clustercfg.VSphereETCDConfig{ - Version: "v3.4.15 # OPTIONAL. Place there the ETCD version you want to use", - }, - OIDCConfig: clustercfg.VSphereOIDCConfig{ - IssuerURL: "https://dex.internal.example.com/ # OPTIONAL. Place here the issuer URL of your oidc provider", - ClientID: "oidc-auth-client # OPTIONAL. Place here the client ID", - CAFile: "/etc/pki/ca-trust/source/anchors/example.com.cer # OPTIONAL. The CA certificate to use", - }, - CRIConfig: clustercfg.VSphereCRIConfig{ - Version: "18.06.2.ce # OPTIONAL. This is the default value for oracle linux docker CRI", - DNS: []string{"1.1.1.1", "8.8.8.8", "# OPTIONAL. Set here your DNS servers"}, - Proxy: "\"HTTP_PROXY=http://systems.example.com:8080\" \"NO_PROXY=.example.com,.group.example.com\"", - Mirrors: []string{"https://mirror.gcr.io", "# OPTIONAL. Set here your dockerhub mirrors"}, - }, - EnvironmentName: "production # The environment name of the cluster", - Config: clustercfg.VSphereConfig{ - DatacenterName: "westeros # Get the name of datacenter from vShpere dashboard", - Datastore: "main # Get the name of datastore from vSphere dashboard", - EsxiHost: []string{"host1", "host2", "host3", "# Names of the hosts where the VMs are going to be created. Use only when not specifying a vSphere cluster"}, - Cluster: "cluster1 # (Optional) vSphere Cluster (resource pool) where the Virtual Machines will be created. Use this option or specify the EsxiHost list when you don't have a cluster.", - }, - NetworkConfig: clustercfg.VSphereNetworkConfig{ - Name: "main-network # The name of the vSphere network", - Gateway: "10.0.0.1 # The IP of the network gateway", - Nameservers: []string{"8.8.4.4", "1.1.1.1"}, - Domain: "localdomain", - IPOffset: 0, - }, - LoadBalancerNode: clustercfg.VSphereKubeLoadBalancer{ - Count: 1, - Template: "ubuntu-20.04 # The name of the base image to use for the VMs", - CustomScriptPath: "./do-something.sh # A script that you want to run after first boot", - }, - MasterNode: clustercfg.VSphereKubeNode{ - Count: 1, - CPU: 1, - MemSize: 4096, - DiskSize: 100, - Template: "ubuntu-20.04 # The name of the base image to use for the VMs", - Labels: map[string]string{ - "environment": "dev. # Node labels. Use it to tag nodes then use it on Kubernetes", - }, - Taints: []string{"key1=value1:NoSchedule. As an example"}, - }, - InfraNode: clustercfg.VSphereKubeNode{ - Count: 1, - CPU: 1, - MemSize: 8192, - DiskSize: 100, - Template: "ubuntu-20.04 # The name of the base image to use for the VMs", - Labels: map[string]string{ - "environment": "dev. # Node labels. Use it to tag nodes then use it on Kubernetes", - }, - Taints: []string{"key1=value1:NoSchedule. As an example"}, - }, - NodePools: []clustercfg.VSphereKubeNode{ - { - Role: "applications", - Count: 1, - CPU: 1, - MemSize: 8192, - DiskSize: 100, - Template: "ubuntu-20.04 # The name of the base image to use for the VMs", - Labels: map[string]string{ - "environment": "dev. # Node labels. Use it to tag nodes then use it on Kubernetes", - }, - Taints: []string{"key1=value1:NoSchedule. As an example"}, - }, - }, - ClusterPODCIDR: "172.21.0.0/16", - ClusterSVCCIDR: "172.23.0.0/16", - ClusterCIDR: "10.2.0.0/16", - SSHPublicKey: []string{ - "/home/foo/.ssh/id_rsa.pub", - }, - } - config.Spec = spec - default: - log.Errorf( - "error creating a template configuration file. Parser not found for %v provisioner", - config.Provisioner, - ) - return fmt.Errorf( - "error creating a template configuration file. Parser not found for %v provisioner", - config.Provisioner, - ) - } - createBase(config) - return nil -} diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go new file mode 100644 index 000000000..b86ee7ab1 --- /dev/null +++ b/internal/dependencies/download.go @@ -0,0 +1,220 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dependencies + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/semver" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + netx "github.com/sighupio/furyctl/internal/x/net" +) + +var ( + ErrDownloadingModule = errors.New("error downloading module") + ErrModuleHasNoVersion = errors.New("module has no version") + ErrModuleHasNoName = errors.New("module has no name") + ErrModuleNotFound = errors.New("module not found") +) + +func NewDownloader(client netx.Client, basePath, binPath string) *Downloader { + return &Downloader{ + client: client, + basePath: basePath, + binPath: binPath, + toolFactory: tools.NewFactory(execx.NewStdExecutor(), tools.FactoryPaths{ + Bin: filepath.Join(basePath, "vendor", "bin"), + }), + } +} + +type Downloader struct { + client netx.Client + toolFactory *tools.Factory + basePath string + binPath string +} + +func (dd *Downloader) DownloadAll(kfd config.KFD) ([]error, []string) { + errs := []error{} + if err := dd.DownloadModules(kfd.Modules); err != nil { + errs = append(errs, err) + } + + if err := dd.DownloadInstallers(kfd.Kubernetes); err != nil { + errs = append(errs, err) + } + + ut, err := dd.DownloadTools(kfd.Tools) + if err != nil { + errs = append(errs, err) + } + + return errs, ut +} + +func (dd *Downloader) DownloadModules(modules config.KFDModules) error { + oldPrefix := "kubernetes-fury" + newPrefix := "fury-kubernetes" + + mods := reflect.ValueOf(modules) + + for i := 0; i < mods.NumField(); i++ { + name := strings.ToLower(mods.Type().Field(i).Name) + version, ok := mods.Field(i).Interface().(string) + + if !ok { + return fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) + } + + if name == "" { + return ErrModuleHasNoName + } + + errs := []error{} + retries := map[string]int{} + + for _, prefix := range []string{oldPrefix, newPrefix} { + src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s.git?ref=%s", prefix, name, version) + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, createURL(prefix, name, version), nil) + if err != nil { + return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + if err := resp.Body.Close(); err != nil { + return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, err) + } + + return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, err) + } + + retries[name]++ + + // Threshold to retry with the new prefix according to the fallback mechanism. + threshold := 2 + + if resp.StatusCode != http.StatusOK { + if retries[name] >= threshold { + errs = append(errs, fmt.Errorf("%w '%s': please check if module exists or credentials are correctly configured", + ErrModuleNotFound, name)) + } + + continue + } + + if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "modules", name)); err != nil { + errs = append(errs, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err)) + + continue + } + + errs = []error{} + + break + } + + if len(errs) > 0 { + return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, errs) + } + } + + return nil +} + +func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes) error { + insts := reflect.ValueOf(installers) + + for i := 0; i < insts.NumField(); i++ { + name := strings.ToLower(insts.Type().Field(i).Name) + + v, ok := insts.Field(i).Interface().(config.KFDProvider) + if !ok { + return fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) + } + + version := v.Installer + + src := fmt.Sprintf("git::git@github.com:sighupio/fury-%s-installer?ref=%s", name, version) + + if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "installers", name)); err != nil { + return fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err) + } + } + + return nil +} + +func (dd *Downloader) DownloadTools(kfdTools config.KFDTools) ([]string, error) { + tls := reflect.ValueOf(kfdTools) + + unsupportedTools := []string{} + + for i := 0; i < tls.NumField(); i++ { + for j := 0; j < tls.Field(i).NumField(); j++ { + name := strings.ToLower(tls.Field(i).Type().Field(j).Name) + + version, ok := tls.Field(i).Field(j).Interface().(config.Tool) + + if !ok { + return unsupportedTools, fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) + } + + tool := dd.toolFactory.Create(name, version.String()) + if tool == nil || !tool.SupportsDownload() { + unsupportedTools = append(unsupportedTools, name) + + continue + } + + dst := filepath.Join(dd.binPath, name, version.String()) + + if err := dd.client.Download(tool.SrcPath(), dst); err != nil { + return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, tool.SrcPath(), err) + } + + if err := tool.Rename(dst); err != nil { + return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrRenamingFile, tool.SrcPath(), err) + } + + err := os.Chmod(filepath.Join(dst, name), iox.FullPermAccess) + if err != nil { + return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrChangingFilePermissions, tool.SrcPath(), err) + } + } + } + + return unsupportedTools, nil +} + +func createURL(prefix, name, version string) string { + ver := semver.EnsurePrefix(version) + + kindPrefix := "releases/tag" + + _, err := semver.NewVersion(ver) + if err != nil { + kindPrefix = "tree" + } + + if token := os.Getenv("GITHUB_TOKEN"); token != "" { + return fmt.Sprintf("https://oauth2:%s@github.com/sighupio/%s-%s/%s/%s", token, prefix, name, kindPrefix, version) + } + + return fmt.Sprintf("https://github.com/sighupio/%s-%s/%s/%s", prefix, name, kindPrefix, version) +} diff --git a/internal/dependencies/envvars/validator.go b/internal/dependencies/envvars/validator.go new file mode 100644 index 000000000..75b26acc2 --- /dev/null +++ b/internal/dependencies/envvars/validator.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package envvars + +import ( + "errors" + "fmt" + "os" +) + +var ErrMissingEnvVar = errors.New("missing environment variable") + +func NewValidator() *Validator { + return &Validator{} +} + +type Validator struct{} + +func (ev *Validator) Validate(kind string) ([]string, []error) { + if kind == "EKSCluster" { + return ev.checkEKSCluster() + } + + return nil, nil +} + +func (*Validator) checkEKSCluster() ([]string, []error) { + oks := make([]string, 0) + errs := make([]error, 0) + + if os.Getenv("AWS_ACCESS_KEY_ID") == "" { + errs = append(errs, fmt.Errorf("AWS_ACCESS_KEY_ID: %w", ErrMissingEnvVar)) + } else { + oks = append(oks, "AWS_ACCESS_KEY_ID") + } + + if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { + errs = append(errs, fmt.Errorf("AWS_SECRET_ACCESS_KEY: %w", ErrMissingEnvVar)) + } else { + oks = append(oks, "AWS_SECRET_ACCESS_KEY") + } + + if os.Getenv("AWS_DEFAULT_REGION") == "" { + errs = append(errs, fmt.Errorf("AWS_DEFAULT_REGION: %w", ErrMissingEnvVar)) + } else { + oks = append(oks, "AWS_DEFAULT_REGION") + } + + return oks, errs +} diff --git a/internal/dependencies/tools/ansible.go b/internal/dependencies/tools/ansible.go new file mode 100644 index 000000000..0d94657db --- /dev/null +++ b/internal/dependencies/tools/ansible.go @@ -0,0 +1,59 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/tool/ansible" +) + +func NewAnsible(runner *ansible.Runner, version string) *Ansible { + return &Ansible{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`ansible \[.*]`), + runner: runner, + splitFn: func(version string) []string { + return strings.Split(version, " ") + }, + trimFn: func(tokens []string) string { + return strings.TrimRight(tokens[len(tokens)-1], "]") + }, + }, + } +} + +type Ansible struct { + arch string + checker *checker + os string + version string +} + +func (*Ansible) SupportsDownload() bool { + return false +} + +func (*Ansible) SrcPath() string { + return "" +} + +func (*Ansible) Rename(_ string) error { + return nil +} + +func (a *Ansible) CheckBinVersion() error { + if err := a.checker.version(a.version); err != nil { + return fmt.Errorf("ansible: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/ansible_test.go b/internal/dependencies/tools/ansible_test.go new file mode 100644 index 000000000..3da4dc819 --- /dev/null +++ b/internal/dependencies/tools/ansible_test.go @@ -0,0 +1,91 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/ansible" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Ansible_SupportsDownload(t *testing.T) { + a := tools.NewAnsible(newAnsibleRunner(), "2.9.27") + + if a.SupportsDownload() != false { + t.Errorf("Ansible download is not supported") + } +} + +func Test_Ansible_SrcPath(t *testing.T) { + a := tools.NewAnsible(newAnsibleRunner(), "2.9.27") + + if a.SrcPath() != "" { + t.Errorf("Wrong ansible src path: want = %s, got = %s", "", a.SrcPath()) + } +} + +func Test_Ansible_Rename(t *testing.T) { + a := tools.NewAnsible(newAnsibleRunner(), "2.9.27") + + if a.Rename("") != nil { + t.Errorf("Ansible rename never returns errors") + } +} + +func Test_Ansible_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "2.9.27", + }, + { + desc: "wrong version installed", + wantVersion: "2.10.0", + wantErr: true, + wantErrMsg: "installed = 2.9.27, expected = 2.10.0", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + a := tools.NewAnsible(newAnsibleRunner(), tC.wantVersion) + + err := a.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newAnsibleRunner() *ansible.Runner { + return ansible.NewRunner(execx.NewFakeExecutor(), ansible.Paths{ + Ansible: "ansible", + }) +} diff --git a/internal/dependencies/tools/awscli.go b/internal/dependencies/tools/awscli.go new file mode 100644 index 000000000..38dc97e62 --- /dev/null +++ b/internal/dependencies/tools/awscli.go @@ -0,0 +1,62 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//nolint:dupl // false positive +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/tool/awscli" +) + +func NewAwscli(runner *awscli.Runner, version string) *Awscli { + return &Awscli{ + arch: "x86_64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`aws-cli/(\S*)`), + runner: runner, + trimFn: func(tokens []string) string { + return tokens[len(tokens)-1] + }, + splitFn: func(version string) []string { + return strings.Split(version, "/") + }, + }, + } +} + +type Awscli struct { + arch string + checker *checker + os string + version string +} + +func (*Awscli) SupportsDownload() bool { + return false +} + +func (*Awscli) SrcPath() string { + // Not used for this tool because it's not downloaded. + return "" +} + +func (*Awscli) Rename(_ string) error { + // Not used for this tool because it's not downloaded. + return nil +} + +func (a *Awscli) CheckBinVersion() error { + if err := a.checker.version(a.version); err != nil { + return fmt.Errorf("aws-cli: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/awscli_test.go b/internal/dependencies/tools/awscli_test.go new file mode 100644 index 000000000..c3459ff32 --- /dev/null +++ b/internal/dependencies/tools/awscli_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/awscli" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Awscli_SupportsDownload(t *testing.T) { + a := tools.NewAwscli(newAwscliRunner(), "2.8.12") + + if a.SupportsDownload() != false { + t.Errorf("Awscli download must not be supported") + } +} + +func Test_Awscli_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "2.8.12", + }, + { + desc: "wrong version installed", + wantVersion: "1.22.0", + wantErr: true, + wantErrMsg: "installed = 2.8.12, expected = 1.22.0", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewAwscli(newAwscliRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newAwscliRunner() *awscli.Runner { + return awscli.NewRunner(execx.NewFakeExecutor(), awscli.Paths{ + Awscli: "aws", + }) +} diff --git a/internal/dependencies/tools/furyagent.go b/internal/dependencies/tools/furyagent.go new file mode 100644 index 000000000..63aa4e511 --- /dev/null +++ b/internal/dependencies/tools/furyagent.go @@ -0,0 +1,75 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/tool/furyagent" +) + +func NewFuryagent(runner *furyagent.Runner, version string) *Furyagent { + return &Furyagent{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`version (\S*)`), + runner: runner, + trimFn: func(tokens []string) string { + return tokens[len(tokens)-1] + }, + splitFn: func(version string) []string { + return strings.Split(version, " ") + }, + }, + } +} + +type Furyagent struct { + arch string + checker *checker + os string + version string +} + +func (*Furyagent) SupportsDownload() bool { + return true +} + +func (f *Furyagent) SrcPath() string { + return fmt.Sprintf( + "https://github.com/sighupio/furyagent/releases/download/%s/furyagent-%s-%s", + semver.EnsurePrefix(f.version), + f.os, + f.arch, + ) +} + +func (f *Furyagent) Rename(basePath string) error { + oldName := fmt.Sprintf("furyagent-%s-%s", f.os, f.arch) + newName := "furyagent" + + err := os.Rename(filepath.Join(basePath, oldName), filepath.Join(basePath, newName)) + if err != nil { + return fmt.Errorf("error while renaming furyagent: %w", err) + } + + return nil +} + +func (f *Furyagent) CheckBinVersion() error { + if err := f.checker.version(f.version); err != nil { + return fmt.Errorf("furyagent: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/furyagent_test.go b/internal/dependencies/tools/furyagent_test.go new file mode 100644 index 000000000..b0764d4c4 --- /dev/null +++ b/internal/dependencies/tools/furyagent_test.go @@ -0,0 +1,134 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/furyagent" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_FuryAgent_SupportsDownload(t *testing.T) { + a := tools.NewFuryagent(newFuryagentRunner(), "0.3.0") + + if a.SupportsDownload() != true { + t.Errorf("Furyagent download must be supported") + } +} + +func Test_FuryAgent_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf( + "https://github.com/sighupio/furyagent/releases/download/v0.3.0/furyagent-%s-amd64", + runtime.GOOS, + ) + + testCases := []struct { + desc string + version string + }{ + { + desc: "0.3.0", + version: "0.3.0", + }, + { + desc: "v0.3.0", + version: "v0.3.0", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewFuryagent(newFuryagentRunner(), tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong furyagent src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_FuryAgent_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf("furyagent-%s-amd64", runtime.GOOS))); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewFuryagent(newFuryagentRunner(), "0.3.0") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming furyagent binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "furyagent")) + if err != nil { + t.Fatalf("Error stating furyagent binary: %v", err) + } + + if info.IsDir() { + t.Errorf("furyagent binary is a directory") + } +} + +func Test_Furyagent_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "0.3.0", + }, + { + desc: "wrong version installed", + wantVersion: "0.4.0", + wantErr: true, + wantErrMsg: "installed = 0.3.0, expected = 0.4.0", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewFuryagent(newFuryagentRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newFuryagentRunner() *furyagent.Runner { + return furyagent.NewRunner(execx.NewFakeExecutor(), furyagent.Paths{ + Furyagent: "furyagent", + }) +} diff --git a/internal/dependencies/tools/kubectl.go b/internal/dependencies/tools/kubectl.go new file mode 100644 index 000000000..4b868811f --- /dev/null +++ b/internal/dependencies/tools/kubectl.go @@ -0,0 +1,68 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/tool/kubectl" +) + +func NewKubectl(runner *kubectl.Runner, version string) *Kubectl { + return &Kubectl{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile("GitVersion:\"([^\"]*)\""), + runner: runner, + trimFn: func(tokens []string) string { + return strings.TrimRight( + strings.TrimLeft(tokens[len(tokens)-1], "\"v"), + "\"", + ) + }, + splitFn: func(version string) []string { + return strings.Split(version, ":") + }, + }, + } +} + +type Kubectl struct { + arch string + checker *checker + os string + version string +} + +func (*Kubectl) SupportsDownload() bool { + return true +} + +func (k *Kubectl) SrcPath() string { + return fmt.Sprintf( + "https://dl.k8s.io/release/%s/bin/%s/%s/kubectl", + semver.EnsurePrefix(k.version), + k.os, + k.arch, + ) +} + +func (*Kubectl) Rename(_ string) error { + return nil +} + +func (k *Kubectl) CheckBinVersion() error { + if err := k.checker.version(k.version); err != nil { + return fmt.Errorf("kubectl: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/kubectl_test.go b/internal/dependencies/tools/kubectl_test.go new file mode 100644 index 000000000..edff34e04 --- /dev/null +++ b/internal/dependencies/tools/kubectl_test.go @@ -0,0 +1,131 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/kubectl" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Kubectl_SupportsDownload(t *testing.T) { + a := tools.NewKubectl(newKubectlRunner(), "1.24.9") + + if a.SupportsDownload() != true { + t.Errorf("kubectl download must be supported") + } +} + +func Test_Kubectl_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf("https://dl.k8s.io/release/v1.24.9/bin/%s/amd64/kubectl", runtime.GOOS) + + testCases := []struct { + desc string + version string + }{ + { + desc: "1.24.9", + version: "1.24.9", + }, + { + desc: "v1.24.9", + version: "v1.24.9", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewKubectl(newKubectlRunner(), tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong kubectl src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_Kubectl_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, "kubectl")); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewKubectl(newKubectlRunner(), "1.24.9") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming kubectl binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "kubectl")) + if err != nil { + t.Fatalf("Error stating kubectl binary: %v", err) + } + + if info.IsDir() { + t.Errorf("kubectl binary is a directory") + } +} + +func Test_Kubectl_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "1.21.1", + }, + { + desc: "wrong version installed", + wantVersion: "1.22.0", + wantErr: true, + wantErrMsg: "installed = 1.21.1, expected = 1.22.0", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewKubectl(newKubectlRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newKubectlRunner() *kubectl.Runner { + return kubectl.NewRunner(execx.NewFakeExecutor(), kubectl.Paths{ + Kubectl: "kubectl", + }, true, true) +} diff --git a/internal/dependencies/tools/kustomize.go b/internal/dependencies/tools/kustomize.go new file mode 100644 index 000000000..6acdf3b2d --- /dev/null +++ b/internal/dependencies/tools/kustomize.go @@ -0,0 +1,67 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//nolint:dupl // false positive +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/tool/kustomize" +) + +func NewKustomize(runner *kustomize.Runner, version string) *Kustomize { + return &Kustomize{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`kustomize/v(\S*)`), + runner: runner, + trimFn: func(tokens []string) string { + return strings.TrimLeft(tokens[len(tokens)-1], "v") + }, + splitFn: func(version string) []string { + return strings.Split(version, "/") + }, + }, + } +} + +type Kustomize struct { + arch string + checker *checker + os string + version string +} + +func (*Kustomize) SupportsDownload() bool { + return true +} + +func (k *Kustomize) SrcPath() string { + return fmt.Sprintf( + "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/%s/kustomize_%s_%s_%s.tar.gz", + semver.EnsurePrefix(k.version), + semver.EnsurePrefix(k.version), + k.os, + k.arch, + ) +} + +func (*Kustomize) Rename(_ string) error { + return nil +} + +func (k *Kustomize) CheckBinVersion() error { + if err := k.checker.version(k.version); err != nil { + return fmt.Errorf("kustomize: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/kustomize_test.go b/internal/dependencies/tools/kustomize_test.go new file mode 100644 index 000000000..e34ffc74e --- /dev/null +++ b/internal/dependencies/tools/kustomize_test.go @@ -0,0 +1,134 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/kustomize" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Kustomize_SupportsDownload(t *testing.T) { + a := tools.NewKustomize(newKustomizeRunner(), "3.5.3") + + if a.SupportsDownload() != true { + t.Errorf("kustomize download must be supported") + } +} + +func Test_Kustomize_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf( + "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.5.3/kustomize_v3.5.3_%s_amd64.tar.gz", + runtime.GOOS, + ) + + testCases := []struct { + desc string + version string + }{ + { + desc: "3.5.3", + version: "3.5.3", + }, + { + desc: "v3.5.3", + version: "v3.5.3", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewKustomize(newKustomizeRunner(), tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong kustomize src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_Kustomize_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, "kustomize")); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewKustomize(newKustomizeRunner(), "3.5.3") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming kustomize binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "kustomize")) + if err != nil { + t.Fatalf("Error stating kustomize binary: %v", err) + } + + if info.IsDir() { + t.Errorf("kustomize binary is a directory") + } +} + +func Test_Kustomize_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "3.9.4", + }, + { + desc: "wrong version installed", + wantVersion: "3.5.3", + wantErr: true, + wantErrMsg: "installed = 3.9.4, expected = 3.5.3", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewKustomize(newKustomizeRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newKustomizeRunner() *kustomize.Runner { + return kustomize.NewRunner(execx.NewFakeExecutor(), kustomize.Paths{ + Kustomize: "kustomize", + }) +} diff --git a/internal/dependencies/tools/openvpn.go b/internal/dependencies/tools/openvpn.go new file mode 100644 index 000000000..95670adcc --- /dev/null +++ b/internal/dependencies/tools/openvpn.go @@ -0,0 +1,60 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//nolint:dupl // false positive +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/tool/openvpn" +) + +func NewOpenvpn(runner *openvpn.Runner, version string) *Openvpn { + return &Openvpn{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`^OpenVPN (\d+.\d+.\d+)`), + runner: runner, + trimFn: func(tokens []string) string { + return tokens[len(tokens)-1] + }, + splitFn: func(version string) []string { + return strings.Split(version, " ") + }, + }, + } +} + +type Openvpn struct { + arch string + checker *checker + os string + version string +} + +func (*Openvpn) SupportsDownload() bool { + return false +} + +func (*Openvpn) SrcPath() string { + return "" +} + +func (*Openvpn) Rename(_ string) error { + return nil +} + +func (o *Openvpn) CheckBinVersion() error { + if err := o.checker.version(o.version); err != nil { + return fmt.Errorf("openvpn: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/openvpn_test.go b/internal/dependencies/tools/openvpn_test.go new file mode 100644 index 000000000..7422321b6 --- /dev/null +++ b/internal/dependencies/tools/openvpn_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/openvpn" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Openvpn_SupportsDownload(t *testing.T) { + t.Parallel() + + a := tools.NewOpenvpn(newOpenvpnRunner(), "2.5.7") + + if a.SupportsDownload() != false { + t.Errorf("Openvpn download is not supported") + } +} + +func Test_Openvpn_SrcPath(t *testing.T) { + t.Parallel() + + a := tools.NewOpenvpn(newOpenvpnRunner(), "2.5.7") + + if a.SrcPath() != "" { + t.Errorf("Wrong openvpn src path: want = %s, got = %s", "", a.SrcPath()) + } +} + +func Test_Openvpn_Rename(t *testing.T) { + t.Parallel() + + a := tools.NewOpenvpn(newOpenvpnRunner(), "2.5.7") + + if a.Rename("") != nil { + t.Errorf("Openvpn rename never returns errors") + } +} + +func Test_Openvpn_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "2.5.7", + }, + { + desc: "wrong version installed", + wantVersion: "2.4.0", + wantErr: true, + wantErrMsg: "installed = 2.5.7, expected = 2.4.0", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + err := tools.NewOpenvpn(newOpenvpnRunner(), tC.wantVersion).CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newOpenvpnRunner() *openvpn.Runner { + return openvpn.NewRunner(execx.NewFakeExecutor(), openvpn.Paths{ + Openvpn: "openvpn", + }) +} diff --git a/internal/dependencies/tools/terraform.go b/internal/dependencies/tools/terraform.go new file mode 100644 index 000000000..bc3fe19b2 --- /dev/null +++ b/internal/dependencies/tools/terraform.go @@ -0,0 +1,67 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//nolint:dupl // false positive +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/tool/terraform" +) + +func NewTerraform(runner *terraform.Runner, version string) *Terraform { + return &Terraform{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile("Terraform .*"), + runner: runner, + trimFn: func(tokens []string) string { + return strings.TrimLeft(tokens[len(tokens)-1], "v") + }, + splitFn: func(version string) []string { + return strings.Split(version, " ") + }, + }, + } +} + +type Terraform struct { + arch string + checker *checker + os string + version string +} + +func (*Terraform) SupportsDownload() bool { + return true +} + +func (t *Terraform) SrcPath() string { + return fmt.Sprintf( + "https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_%s.zip", + semver.EnsureNoPrefix(t.version), + semver.EnsureNoPrefix(t.version), + t.os, + t.arch, + ) +} + +func (*Terraform) Rename(_ string) error { + return nil +} + +func (t *Terraform) CheckBinVersion() error { + if err := t.checker.version(t.version); err != nil { + return fmt.Errorf("terraform: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/terraform_test.go b/internal/dependencies/tools/terraform_test.go new file mode 100644 index 000000000..6878416b7 --- /dev/null +++ b/internal/dependencies/tools/terraform_test.go @@ -0,0 +1,134 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Terraform_SupportsDownload(t *testing.T) { + a := tools.NewTerraform(newTerraformRunner(), "1.2.9") + + if a.SupportsDownload() != true { + t.Errorf("terraform download must be supported") + } +} + +func Test_Terraform_SrcPath(t *testing.T) { + wantSrcPath := fmt.Sprintf( + "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_%s_amd64.zip", + runtime.GOOS, + ) + + testCases := []struct { + desc string + version string + }{ + { + desc: "1.2.9", + version: "1.2.9", + }, + { + desc: "v1.2.9", + version: "v1.2.9", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + fa := tools.NewTerraform(newTerraformRunner(), tC.version) + if fa.SrcPath() != wantSrcPath { + t.Errorf("Wrong terraform src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) + } + }) + } +} + +func Test_Terraform_Rename(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "furyctl-test-") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + + if _, err := os.Create(filepath.Join(tmpDir, "terraform")); err != nil { + t.Fatalf("error creating temp file: %v", err) + } + + fa := tools.NewTerraform(newTerraformRunner(), "1.2.9") + + if err := fa.Rename(tmpDir); err != nil { + t.Fatalf("Error renaming terraform binary: %v", err) + } + + info, err := os.Stat(filepath.Join(tmpDir, "terraform")) + if err != nil { + t.Fatalf("Error stating terraform binary: %v", err) + } + + if info.IsDir() { + t.Errorf("terraform binary is a directory") + } +} + +func Test_Terraform_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "0.15.4", + }, + { + desc: "wrong version installed", + wantVersion: "1.3.0", + wantErr: true, + wantErrMsg: "installed = 0.15.4, expected = 1.3.0", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewTerraform(newTerraformRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newTerraformRunner() *terraform.Runner { + return terraform.NewRunner(execx.NewFakeExecutor(), terraform.Paths{ + Terraform: "terraform", + }) +} diff --git a/internal/dependencies/tools/test_data/aws-cli/2.8.12/aws b/internal/dependencies/tools/test_data/aws-cli/2.8.12/aws new file mode 100755 index 000000000..45ec3ca6d --- /dev/null +++ b/internal/dependencies/tools/test_data/aws-cli/2.8.12/aws @@ -0,0 +1,5 @@ +#!/bin/sh + +cat <\n"+ + "Compile time defines: enable_async_push=no enable_comp_stub=no enable_crypto_ofb_cfb=yes enable_debug=no enable_def_auth=yes enable_dependency_tracking=no enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown enable_fast_install=needless enable_fragment=yes enable_iproute2=no enable_libtool_lock=yes enable_lz4=yes enable_lzo=yes enable_management=yes enable_multihome=yes enable_pam_dlopen=no enable_pedantic=no enable_pf=yes enable_pkcs11=yes enable_plugin_auth_pam=yes enable_plugin_down_root=yes enable_plugins=yes enable_port_share=yes enable_selinux=no enable_shared=yes enable_shared_with_static_runtimes=no enable_silent_rules=no enable_small=no enable_static=yes enable_strict=no enable_strict_options=no enable_systemd=no enable_werror=no enable_win32_dll=yes enable_x509_alt_username=no with_aix_soname=aix with_crypto_library=openssl with_gnu_ld=no with_mem_check=no with_openssl_engine=auto with_sysroot=no\n") + } + case "terraform": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "Terraform v0.15.4\non darwin_amd64") + } + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go new file mode 100644 index 000000000..b98d4ba93 --- /dev/null +++ b/internal/dependencies/tools/validator.go @@ -0,0 +1,62 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "errors" + "reflect" + "strings" + + "github.com/sighupio/fury-distribution/pkg/config" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +var ( + ErrEmptyToolVersion = errors.New("empty tool version") + ErrWrongToolVersion = errors.New("wrong tool version") +) + +func NewValidator(executor execx.Executor, binPath string) *Validator { + return &Validator{ + executor: executor, + toolFactory: NewFactory(executor, FactoryPaths{ + Bin: binPath, + }), + } +} + +type Validator struct { + executor execx.Executor + toolFactory *Factory +} + +func (tv *Validator) Validate(kfdManifest config.KFD) ([]string, []error) { + var ( + oks []string + errs []error + ) + + tls := reflect.ValueOf(kfdManifest.Tools) + for i := 0; i < tls.NumField(); i++ { + for j := 0; j < tls.Field(i).NumField(); j++ { + if version, ok := tls.Field(i).Field(j).Interface().(config.Tool); ok { + if version.String() == "" { + continue + } + + name := strings.ToLower(tls.Field(i).Type().Field(j).Name) + + tool := tv.toolFactory.Create(name, version.String()) + if err := tool.CheckBinVersion(); err != nil { + errs = append(errs, err) + } else { + oks = append(oks, name) + } + } + } + } + + return oks, errs +} diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go new file mode 100644 index 000000000..6c57c84f0 --- /dev/null +++ b/internal/dependencies/tools/validator_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "errors" + "strings" + "testing" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/dependencies/tools" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Validator_Validate(t *testing.T) { + testCases := []struct { + desc string + manifest config.KFD + wantOks []string + wantErrs []error + }{ + { + desc: "all tools are installed in their correct version", + manifest: config.KFD{ + Tools: config.KFDTools{ + Common: config.Common{ + Kubectl: config.Tool{Version: "1.21.1"}, + Kustomize: config.Tool{Version: "3.9.4"}, + Terraform: config.Tool{Version: "0.15.4"}, + Furyagent: config.Tool{Version: "0.3.0"}, + }, + }, + }, + wantOks: []string{ + "kubectl", + "kustomize", + "terraform", + "furyagent", + }, + }, + { + desc: "all tools are installed in their wrong version", + manifest: config.KFD{ + Tools: config.KFDTools{ + Common: config.Common{ + Kubectl: config.Tool{Version: "1.22.0"}, + Kustomize: config.Tool{Version: "3.5.3"}, + Terraform: config.Tool{Version: "1.3.0"}, + Furyagent: config.Tool{Version: "0.4.0"}, + }, + }, + }, + wantErrs: []error{ + errors.New("furyagent: wrong tool version - installed = 0.3.0, expected = 0.4.0"), + errors.New("kubectl: wrong tool version - installed = 1.21.1, expected = 1.22.0"), + errors.New("kustomize: wrong tool version - installed = 3.9.4, expected = 3.5.3"), + errors.New("terraform: wrong tool version - installed = 0.15.4, expected = 1.3.0"), + }, + wantOks: []string{}, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + v := tools.NewValidator(execx.NewFakeExecutor(), "test_data") + + oks, errs := v.Validate(tC.manifest) + + if len(oks) != len(tC.wantOks) { + t.Errorf("Expected %d oks, got %d - %v", len(tC.wantOks), len(oks), oks) + } + + if len(errs) != len(tC.wantErrs) { + t.Errorf("Expected %d errors, got %d - %v", len(tC.wantErrs), len(errs), errs) + } + + for _, ok := range oks { + found := false + for _, wantOk := range tC.wantOks { + if ok == wantOk { + found = true + + break + } + } + + if !found { + t.Errorf("Unexpected ok: %s", ok) + } + } + + for _, err := range errs { + found := false + for _, wantErr := range tC.wantErrs { + if strings.Trim(err.Error(), "\n") == strings.Trim(wantErr.Error(), "\n") { + found = true + + break + } + } + + if !found { + t.Errorf("Unexpected error: %s", err) + } + } + }) + } +} diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go new file mode 100644 index 000000000..4d8df4ac0 --- /dev/null +++ b/internal/dependencies/validate.go @@ -0,0 +1,44 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dependencies + +import ( + "errors" + "fmt" + + "github.com/sighupio/furyctl/internal/dependencies/envvars" + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/distribution" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +var ( + errValidatingTools = errors.New("errors validating tools") + errValidatingEnv = errors.New("errors validating env vars") +) + +func NewValidator(executor execx.Executor, binPath string) *Validator { + return &Validator{ + toolsValidator: tools.NewValidator(executor, binPath), + envVarsValidator: envvars.NewValidator(), + } +} + +type Validator struct { + toolsValidator *tools.Validator + envVarsValidator *envvars.Validator +} + +func (v *Validator) Validate(res distribution.DownloadResult) error { + if _, errs := v.toolsValidator.Validate(res.DistroManifest); len(errs) > 0 { + return fmt.Errorf("%w: %v", errValidatingTools, errs) + } + + if _, errs := v.envVarsValidator.Validate(res.MinimalConf.Kind); len(errs) > 0 { + return fmt.Errorf("%w: %v", errValidatingEnv, errs) + } + + return nil +} diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 10b02f48e..7e6433a56 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -11,132 +11,107 @@ import ( "path/filepath" "strings" + "github.com/go-playground/validator/v10" "github.com/sirupsen/logrus" - "github.com/sighupio/furyctl/internal/netx" - "github.com/sighupio/furyctl/internal/osx" - "github.com/sighupio/furyctl/internal/semver" - "github.com/sighupio/furyctl/internal/yaml" + "github.com/sighupio/fury-distribution/pkg/config" + netx "github.com/sighupio/furyctl/internal/x/net" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const DefaultBaseUrl = "https://git@github.com/sighupio/fury-distribution?ref=%s" +const DefaultBaseURL = "https://git@github.com/sighupio/fury-distribution?ref=%s" var ( - ErrCreatingTempDir = errors.New("error creating temp dir") - ErrDownloadingFolder = errors.New("error downloading folder") - ErrMergeCompleteConfig = errors.New("error merging complete config") - ErrMergeDistroConfig = errors.New("error merging distribution config") - ErrWriteFile = errors.New("error writing file") - ErrYamlMarshalFile = errors.New("error marshaling yaml file") - ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") + ErrChangingFilePermissions = errors.New("error changing file permissions") + ErrCreatingTempDir = errors.New("error creating temp dir") + ErrDownloadingFolder = errors.New("error downloading folder") + ErrMergeCompleteConfig = errors.New("error merging complete config") + ErrMergeDistroConfig = errors.New("error merging distribution config") + ErrRenamingFile = errors.New("error renaming file") + ErrResolvingAbsPath = errors.New("error resolving absolute path") + ErrValidateConfig = errors.New("error validating config") + ErrWriteFile = errors.New("error writing file") + ErrYamlMarshalFile = errors.New("error marshaling yaml file") + ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") ) -type downloadResult struct { +type DownloadResult struct { RepoPath string - MinimalConf FuryctlConfig - DistroManifest Manifest + MinimalConf config.Furyctl + DistroManifest config.KFD } -func NewDownloader(client netx.Client, debug bool) *Downloader { +func NewDownloader(client netx.Client) *Downloader { return &Downloader{ - client: client, - debug: debug, + client: client, + validate: config.NewValidator(), } } type Downloader struct { - client netx.Client - debug bool + client netx.Client + validate *validator.Validate } func (d *Downloader) Download( - furyctlBinVersion string, distroLocation string, furyctlConfPath string, -) (downloadResult, error) { - minimalConf, err := yaml.FromFileV3[FuryctlConfig](furyctlConfPath) +) (DownloadResult, error) { + minimalConf, err := yamlx.FromFileV3[config.Furyctl](furyctlConfPath) if err != nil { - return downloadResult{}, err + return DownloadResult{}, fmt.Errorf("%w: %s", ErrYamlUnmarshalFile, err) } - furyctlConfSemVer := minimalConf.Spec.DistributionVersion - - if furyctlBinVersion != "unknown" { - furyctlBinSemVer, err := semver.NewVersion(fmt.Sprintf("v%s", furyctlBinVersion)) - if err != nil { - return downloadResult{}, err - } + return d.DoDownload(distroLocation, minimalConf) +} - if !semver.SameMinor(furyctlConfSemVer, furyctlBinSemVer) { - logrus.Warnf( - "this version of furyctl ('%s') does not support distribution version '%s', results may be inaccurate", - furyctlBinVersion, - furyctlConfSemVer, - ) - } +func (d *Downloader) DoDownload( + distroLocation string, + minimalConf config.Furyctl, +) (DownloadResult, error) { + if err := d.validate.Struct(minimalConf); err != nil { + return DownloadResult{}, fmt.Errorf("invalid furyctl config: %w", err) } if distroLocation == "" { - distroLocation = fmt.Sprintf(DefaultBaseUrl, furyctlConfSemVer.String()) + distroLocation = fmt.Sprintf(DefaultBaseURL, minimalConf.Spec.DistributionVersion) + } + + if strings.HasPrefix(distroLocation, ".") { + var err error + if distroLocation, err = filepath.Abs(distroLocation); err != nil { + return DownloadResult{}, fmt.Errorf("%w: %v", ErrResolvingAbsPath, err) + } } baseDst, err := os.MkdirTemp("", "furyctl-") if err != nil { - return downloadResult{}, fmt.Errorf("%w: %v", ErrCreatingTempDir, err) + return DownloadResult{}, fmt.Errorf("%w: %v", ErrCreatingTempDir, err) } + src := distroLocation dst := filepath.Join(baseDst, "data") logrus.Debugf("Downloading '%s' in '%s'", src, dst) if err := netx.NewGoGetterClient().Download(src, dst); err != nil { - return downloadResult{}, fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) - } - - if !d.debug { - defer osx.CleanupTempDir(filepath.Base(dst)) + return DownloadResult{}, fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } kfdPath := filepath.Join(dst, "kfd.yaml") - kfdManifest, err := yaml.FromFileV3[Manifest](kfdPath) + + kfdManifest, err := yamlx.FromFileV3[config.KFD](kfdPath) if err != nil { - return downloadResult{}, err + return DownloadResult{}, err } - if !semver.SamePatch(furyctlConfSemVer, kfdManifest.Version) { - return downloadResult{}, fmt.Errorf( - "versions mismatch: furyctl.yaml = '%s', furyctl binary = '%s'", - furyctlConfSemVer.String(), - kfdManifest.Version.String(), - ) + if err := d.validate.Struct(kfdManifest); err != nil { + return DownloadResult{}, fmt.Errorf("invalid kfd config: %w", err) } - return downloadResult{ + return DownloadResult{ RepoPath: dst, MinimalConf: minimalConf, DistroManifest: kfdManifest, }, nil } - -func GetSchemaPath(basePath string, conf FuryctlConfig) (string, error) { - avp := strings.Split(conf.ApiVersion, "/") - - if len(avp) < 2 { - return "", fmt.Errorf("invalid apiVersion: %s", conf.ApiVersion) - } - - ns := strings.Replace(avp[0], ".sighup.io", "", 1) - ver := avp[1] - - if conf.Kind == "" { - return "", fmt.Errorf("kind is empty") - } - - filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(conf.Kind.String()), ns, ver) - - return filepath.Join(basePath, "schemas", filename), nil -} - -func GetDefaultPath(basePath string) string { - return filepath.Join(basePath, "furyctl-defaults.yaml") -} diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index 02004c6e8..9c4f45a97 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build integration + package distribution_test import ( @@ -10,55 +12,50 @@ import ( "testing" "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/netx" - "github.com/sighupio/furyctl/internal/semver" + netx "github.com/sighupio/furyctl/internal/x/net" ) func Test_Downloader_Download(t *testing.T) { testCases := []struct { desc string - furyctlBinVer string wantApiVer string wantKind string wantDistroVer string }{ { desc: "unknown furyctl version", - furyctlBinVer: "unknown", wantApiVer: "kfd.sighup.io/v1alpha2", wantKind: "EKSCluster", - wantDistroVer: "v1.23.3", + wantDistroVer: "v1.24.1", }, { desc: "compatible furyctl version", - furyctlBinVer: "1.23.0", wantApiVer: "kfd.sighup.io/v1alpha2", wantKind: "EKSCluster", - wantDistroVer: "v1.23.3", + wantDistroVer: "v1.24.1", }, { desc: "older furyctl version", - furyctlBinVer: "1.20.0", wantApiVer: "kfd.sighup.io/v1alpha2", wantKind: "EKSCluster", - wantDistroVer: "v1.23.3", + wantDistroVer: "v1.24.1", }, } for _, tC := range testCases { tC := tC t.Run(tC.desc, func(t *testing.T) { - distroLocation, err := filepath.Abs(fmt.Sprintf("../../test/data/%s/distro", tC.wantDistroVer)) + distroPath := fmt.Sprintf("../../test/data/integration/%s/distro", tC.wantDistroVer) + absDistroPath, err := filepath.Abs(distroPath) if err != nil { t.Fatal(err) } - d := distribution.NewDownloader(netx.NewGoGetterClient(), true) + d := distribution.NewDownloader(netx.NewGoGetterClient()) res, err := d.Download( - tC.furyctlBinVer, - distroLocation, - fmt.Sprintf("../../test/data/%s/furyctl.yaml", tC.wantDistroVer), + absDistroPath, + fmt.Sprintf("../../test/data/integration/%s/furyctl.yaml", tC.wantDistroVer), ) if err != nil { t.Fatal(err) @@ -68,13 +65,13 @@ func Test_Downloader_Download(t *testing.T) { t.Errorf("expected RepoPath, got empty string") } - if res.MinimalConf.ApiVersion != tC.wantApiVer { - t.Errorf("ApiVersion: want = %s, got = %s", tC.wantApiVer, res.MinimalConf.ApiVersion) + if res.MinimalConf.APIVersion != tC.wantApiVer { + t.Errorf("ApiVersion: want = %s, got = %s", tC.wantApiVer, res.MinimalConf.APIVersion) } - if res.MinimalConf.Kind.String() != tC.wantKind { + if res.MinimalConf.Kind != tC.wantKind { t.Errorf("Kind: want = %s, got = %s", tC.wantKind, res.MinimalConf.Kind) } - if res.MinimalConf.Spec.DistributionVersion != semver.Version(tC.wantDistroVer) { + if res.MinimalConf.Spec.DistributionVersion != tC.wantDistroVer { t.Errorf( "DistributionVersion: want = %s, got = %s", tC.wantDistroVer, @@ -82,92 +79,9 @@ func Test_Downloader_Download(t *testing.T) { ) } - if res.DistroManifest.Version != semver.Version(tC.wantDistroVer) { + if res.DistroManifest.Version != tC.wantDistroVer { t.Errorf("ApiVersion: want = %s, got = %s", tC.wantDistroVer, res.DistroManifest.Version) } }) } } - -func TestGetSchemaPath(t *testing.T) { - tests := []struct { - name string - basePath string - conf distribution.FuryctlConfig - want string - wantErr error - }{ - { - name: "test with base path", - basePath: "testpath", - conf: distribution.FuryctlConfig{ - ApiVersion: "kfd.sighup.io/v1alpha2", - Kind: "EKSCluster", - Spec: struct { - DistributionVersion semver.Version `yaml:"distributionVersion"` - }{}, - }, - want: fmt.Sprintf("%s", filepath.Join( - "testpath", - "schemas", - "ekscluster-kfd-v1alpha2.json", - )), - wantErr: nil, - }, - { - name: "test without base path", - basePath: "", - conf: distribution.FuryctlConfig{ - ApiVersion: "kfd.sighup.io/v1alpha2", - Kind: "EKSCluster", - Spec: struct { - DistributionVersion semver.Version `yaml:"distributionVersion"` - }{}, - }, - want: fmt.Sprintf("%s", filepath.Join("schemas", "ekscluster-kfd-v1alpha2.json")), - wantErr: nil, - }, - { - name: "test with invalid apiVersion", - basePath: "", - conf: distribution.FuryctlConfig{ - ApiVersion: "", - Kind: "EKSCluster", - Spec: struct { - DistributionVersion semver.Version `yaml:"distributionVersion"` - }{}, - }, - want: "", - wantErr: fmt.Errorf("invalid apiVersion: "), - }, - { - name: "test with invalid kind", - basePath: "", - conf: distribution.FuryctlConfig{ - ApiVersion: "kfd.sighup.io/v1alpha2", - Kind: "", - Spec: struct { - DistributionVersion semver.Version `yaml:"distributionVersion"` - }{}, - }, - want: "", - wantErr: fmt.Errorf("kind is empty"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := distribution.GetSchemaPath(tt.basePath, tt.conf) - if err != nil { - if err.Error() != tt.wantErr.Error() { - t.Errorf("distribution.GetSchemaPath() error = %v, wantErr %v", err, tt.wantErr) - } - - return - } - - if got != tt.want { - t.Errorf("distribution.GetSchemaPath() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/distribution/model.go b/internal/distribution/model.go deleted file mode 100644 index 98c96246b..000000000 --- a/internal/distribution/model.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package distribution - -import "github.com/sighupio/furyctl/internal/semver" - -type Kind string - -func (k Kind) String() string { - return string(k) -} - -func (k Kind) Equals(kk Kind) bool { - return k == kk -} - -type FuryctlConfig struct { - ApiVersion string `yaml:"apiVersion"` - Kind Kind `yaml:"kind"` - Spec struct { - DistributionVersion semver.Version `yaml:"distributionVersion"` - } `yaml:"spec"` -} - -func (c *FuryctlConfig) UnmarshalYAML(unmarshal func(any) error) error { - type rawFuryctlConfig FuryctlConfig - raw := rawFuryctlConfig{} - if err := unmarshal(&raw); err != nil { - return err - } - *c = FuryctlConfig(raw) - - distroVersion, err := semver.NewVersion(string(c.Spec.DistributionVersion)) - if err != nil { - return err - } - - c.Spec.DistributionVersion = distroVersion - - return nil -} - -type ManifestModules struct { - Auth string `yaml:"auth"` - Dr string `yaml:"dr"` - Ingress string `yaml:"ingress"` - Logging string `yaml:"logging"` - Monitoring string `yaml:"monitoring"` - Opa string `yaml:"opa"` -} - -type ManifestProvider struct { - Version string `yaml:"version"` - Installer string `yaml:"installer"` -} - -type ManifestKubernetes struct { - Eks ManifestProvider `yaml:"eks"` -} - -type ManifestSchemas struct { - Eks []struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - } `yaml:"eks"` -} - -type ManifestTools struct { - Ansible string `yaml:"ansible"` - Furyagent string `yaml:"furyagent"` - Kubectl string `yaml:"kubectl"` - Kustomize string `yaml:"kustomize"` - Terraform string `yaml:"terraform"` -} - -type Manifest struct { - Version semver.Version `yaml:"version"` - Modules ManifestModules `yaml:"modules"` - Kubernetes ManifestKubernetes `yaml:"kubernetes"` - FuryctlSchemas ManifestSchemas `yaml:"furyctlSchemas"` - Tools ManifestTools `yaml:"tools"` -} - -func (m *Manifest) UnmarshalYAML(unmarshal func(any) error) error { - type rawKfdManifest Manifest - raw := rawKfdManifest{} - if err := unmarshal(&raw); err != nil { - return err - } - *m = Manifest(raw) - - version, err := semver.NewVersion(m.Version.String()) - if err != nil { - return err - } - - m.Version = version - - return nil -} diff --git a/internal/distribution/path.go b/internal/distribution/path.go new file mode 100644 index 000000000..98c136706 --- /dev/null +++ b/internal/distribution/path.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + + "github.com/sighupio/fury-distribution/pkg/config" +) + +const ValidLength = 2 + +var ( + errKindIsEmpty = errors.New("kind is empty") + errInvalidAPIVersion = errors.New("invalid apiVersion") +) + +func GetConfigTemplatePath(basePath string, conf config.Furyctl) (string, error) { + return getPath(basePath, conf, "%s-%s-%s.yaml.tpl", "templates/config") +} + +func GetSchemaPath(basePath string, conf config.Furyctl) (string, error) { + return getPath(basePath, conf, "%s-%s-%s.json", "schemas") +} + +func GetDefaultsPath(basePath string) string { + return filepath.Join(basePath, "furyctl-defaults.yaml") +} + +func getPath(basePath string, conf config.Furyctl, fnameTpl, subDir string) (string, error) { + avp := strings.Split(conf.APIVersion, "/") + + if len(avp) < ValidLength { + return "", fmt.Errorf("%w: %s", errInvalidAPIVersion, conf.APIVersion) + } + + ns := strings.Replace(avp[0], ".sighup.io", "", 1) + ver := avp[1] + + if conf.Kind == "" { + return "", errKindIsEmpty + } + + filename := fmt.Sprintf(fnameTpl, strings.ToLower(conf.Kind), ns, ver) + + return filepath.Join(basePath, subDir, filename), nil +} diff --git a/internal/distribution/path_test.go b/internal/distribution/path_test.go new file mode 100644 index 000000000..3e33b2a1e --- /dev/null +++ b/internal/distribution/path_test.go @@ -0,0 +1,174 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package distribution_test + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/distribution" +) + +func TestGetTemplatePath(t *testing.T) { + tests := []struct { + name string + basePath string + conf config.Furyctl + want string + wantErr error + }{ + { + name: "test with base path", + basePath: "testpath", + conf: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{}, + }, + want: fmt.Sprintf("%s", filepath.Join( + "testpath", + "templates/config", + "ekscluster-kfd-v1alpha2.yaml.tpl", + )), + wantErr: nil, + }, + { + name: "test without base path", + basePath: "", + conf: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{}, + }, + want: fmt.Sprintf("%s", filepath.Join("templates/config", "ekscluster-kfd-v1alpha2.yaml.tpl")), + wantErr: nil, + }, + { + name: "test with invalid apiVersion", + basePath: "", + conf: config.Furyctl{ + APIVersion: "", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{}, + }, + want: "", + wantErr: fmt.Errorf("invalid apiVersion: "), + }, + { + name: "test with invalid kind", + basePath: "", + conf: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "", + Spec: config.FuryctlSpec{}, + }, + want: "", + wantErr: fmt.Errorf("kind is empty"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := distribution.GetConfigTemplatePath(tt.basePath, tt.conf) + if err != nil { + if err.Error() != tt.wantErr.Error() { + t.Errorf("distribution.GetTemplatePath() error = %v, wantErr %v", err, tt.wantErr) + } + + return + } + + if got != tt.want { + t.Errorf("distribution.GetTemplatePath() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetSchemaPath(t *testing.T) { + tests := []struct { + name string + basePath string + conf config.Furyctl + want string + wantErr error + }{ + { + name: "test with base path", + basePath: "testpath", + conf: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{}, + }, + want: fmt.Sprintf("%s", filepath.Join( + "testpath", + "schemas", + "ekscluster-kfd-v1alpha2.json", + )), + wantErr: nil, + }, + { + name: "test without base path", + basePath: "", + conf: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{}, + }, + want: fmt.Sprintf("%s", filepath.Join("schemas", "ekscluster-kfd-v1alpha2.json")), + wantErr: nil, + }, + { + name: "test with invalid apiVersion", + basePath: "", + conf: config.Furyctl{ + APIVersion: "", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{}, + }, + want: "", + wantErr: fmt.Errorf("invalid apiVersion: "), + }, + { + name: "test with invalid kind", + basePath: "", + conf: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "", + Spec: config.FuryctlSpec{}, + }, + want: "", + wantErr: fmt.Errorf("kind is empty"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := distribution.GetSchemaPath(tt.basePath, tt.conf) + if err != nil { + if err.Error() != tt.wantErr.Error() { + t.Errorf("distribution.GetSchemaPath() error = %v, wantErr %v", err, tt.wantErr) + } + + return + } + + if got != tt.want { + t.Errorf("distribution.GetSchemaPath() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetDefaultsPath(t *testing.T) { + dp := distribution.GetDefaultsPath("/tmp") + + if dp != "/tmp/furyctl-defaults.yaml" { + t.Errorf("expected /tmp/furyctl-defaults.yaml, got %s", dp) + } +} diff --git a/internal/execx/exec_test.go b/internal/execx/exec_test.go deleted file mode 100644 index 43401e2de..000000000 --- a/internal/execx/exec_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package execx_test - -import ( - "testing" - - "github.com/sighupio/furyctl/internal/execx" -) - -func Test_StdExecutor_Command(t *testing.T) { - e := execx.NewStdExecutor() - - cmd := e.Command("echo", "hello go world") - if cmd == nil { - t.Fatalf("expected command to be not nil") - } - - out, err := cmd.Output() - if err != nil { - t.Fatalf("expected command to be executed without errors: %v", err) - } - - if string(out) != "hello go world\n" { - t.Errorf("want = 'hello go world', got = '%s'", string(out)) - } -} diff --git a/internal/io/fs.go b/internal/io/fs.go deleted file mode 100644 index 8d43f970c..000000000 --- a/internal/io/fs.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package io - -import ( - "bytes" - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/sirupsen/logrus" -) - -func CheckDirIsEmpty(target string) error { - if _, err := os.Stat(target); os.IsNotExist(err) { - return nil - } - - return filepath.Walk(target, func(path string, info os.FileInfo, err error) error { - if err != nil { - return fmt.Errorf("the target directory is not empty, error while checking %s: %w", path, err) - } - - return fmt.Errorf("the target directory is not empty: %s", path) - }) -} - -func AppendBufferToFile(b bytes.Buffer, target string) error { - destination, err := os.OpenFile(target, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - - defer destination.Close() - - _, err = b.WriteTo(destination) - if err != nil { - return err - } - - return nil -} - -func CopyBufferToFile(b bytes.Buffer, source, target string) error { - if strings.TrimSpace(b.String()) == "" { - logrus.Printf("%s --> resulted in an empty file (%d bytes). Skipping.\n", source, b.Len()) - return nil - } - - logrus.Printf("%s --> %s\n", source, target) - - destination, err := os.Create(target) - if err != nil { - return err - } - - defer destination.Close() - - _, err = b.WriteTo(destination) - if err != nil { - return err - } - - return nil -} - -func CopyFromSourceToTarget(src, dst string) (int64, error) { - sourceFileStat, err := os.Stat(src) - if err != nil { - return 0, err - } - - if !sourceFileStat.Mode().IsRegular() { - return 0, fmt.Errorf("%s is not a regular file", src) - } - - source, err := os.Open(src) - if err != nil { - return 0, err - } - - defer source.Close() - - destination, err := os.Create(dst) - if err != nil { - return 0, err - } - - defer destination.Close() - - return io.Copy(destination, source) -} - -// EnsureDir creates the directories to host the file. -// Example: hello/world.md will create the hello dir if it does not exists. -func EnsureDir(fileName string) (err error) { - dirName := filepath.Dir(fileName) - if _, serr := os.Stat(dirName); serr != nil { - err := os.MkdirAll(dirName, os.ModePerm) - if err != nil { - return err - } - } - return nil -} diff --git a/internal/merge/merge.go b/internal/merge/merge.go index 926118839..f3df67223 100644 --- a/internal/merge/merge.go +++ b/internal/merge/merge.go @@ -20,15 +20,23 @@ func NewMerger(b, c Mergeable) *Merger { } } +func (m *Merger) GetBase() *Mergeable { + return &m.base +} + +func (m *Merger) GetCustom() *Mergeable { + return &m.custom +} + func (m *Merger) Merge() (map[any]any, error) { preparedBase, err := m.base.Get() if err != nil { - return nil, fmt.Errorf("incorrect base file, %s", err.Error()) + return nil, fmt.Errorf("incorrect base file, %w", err) } preparedCustom, err := m.custom.Get() if err != nil { - return preparedBase, nil + return nil, fmt.Errorf("incorrect custom file, %w", err) } mergedSection := deepCopy(preparedBase, preparedCustom) @@ -43,16 +51,20 @@ func deepCopy(a, b map[any]any) map[any]any { for k, v := range a { out[k] = v } + for k, v := range b { if v, ok := v.(map[any]any); ok { if bv, ok := out[k]; ok { if bv, ok := bv.(map[any]any); ok { out[k] = deepCopy(bv, v) + continue } } } + out[k] = v } + return out } diff --git a/internal/merge/merge_test.go b/internal/merge/merge_test.go index 59fedf689..ff8966b9e 100644 --- a/internal/merge/merge_test.go +++ b/internal/merge/merge_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package merge_test import ( diff --git a/internal/merge/model.go b/internal/merge/model.go index c2af50119..9d344ded0 100644 --- a/internal/merge/model.go +++ b/internal/merge/model.go @@ -5,8 +5,16 @@ package merge import ( + "errors" "fmt" "strings" + + mapx "github.com/sighupio/furyctl/internal/x/map" +) + +var ( + errCannotAccessKey = errors.New("cannot access key") + errInvalidData = errors.New("data structure is invalid on key") ) type Mergeable interface { @@ -28,6 +36,14 @@ func NewDefaultModel(content map[any]any, path string) *DefaultModel { } } +func NewDefaultModelFromStruct(content any, path string, skipEmpty bool) *DefaultModel { + builder := mapx.NewBuilder(skipEmpty) + + c := builder.FromStruct(content, "json") + + return NewDefaultModel(c, path) +} + func (b *DefaultModel) Content() map[any]any { return b.content } @@ -39,17 +55,17 @@ func (b *DefaultModel) Path() string { func (b *DefaultModel) Get() (map[any]any, error) { ret := b.content - fields := strings.Split((*b).path[1:], ".") + fields := strings.Split(b.path[1:], ".") for _, f := range fields { mapAtKey, ok := ret[f] if !ok { - return nil, fmt.Errorf("cannot access key %s on map", f) + return nil, fmt.Errorf("%w %s on map", errCannotAccessKey, f) } ret, ok = mapAtKey.(map[any]any) if !ok { - return nil, fmt.Errorf("data structure is invalid on key %s", f) + return nil, fmt.Errorf("%w %s", errInvalidData, f) } } @@ -64,12 +80,12 @@ func (b *DefaultModel) Walk(mergedSection map[any]any) error { for _, f := range fields[:len(fields)-1] { _, ok := ret[f] if !ok { - return fmt.Errorf("cannot access key %s on map", f) + return fmt.Errorf("%w %s on map", errCannotAccessKey, f) } ret, ok = ret[f].(map[any]any) if !ok { - return fmt.Errorf("data structure is invalid on key %s", f) + return fmt.Errorf("%w %s", errInvalidData, f) } } diff --git a/internal/merge/model_test.go b/internal/merge/model_test.go index a471a65ea..b6c155c70 100644 --- a/internal/merge/model_test.go +++ b/internal/merge/model_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package merge_test import ( @@ -33,6 +35,53 @@ func TestNewDefaultModel(t *testing.T) { assert.Equal(t, path, model.Path()) } +func TestNewDefaultModelFromStruct(t *testing.T) { + type TestSubStruct struct { + TestOptInt *int `json:"testOptInt,omitempty"` + testUnexposed string + TestUntaggedString string + } + + type TestStruct struct { + TestString string `json:"testString"` + TestOptionalSub *TestSubStruct `json:"testOptionalSub"` + TestSub TestSubStruct `json:"testSub"` + } + + type TestContent struct { + Data TestStruct `json:"data"` + } + + content := TestContent{ + Data: TestStruct{ + TestString: "lorem ipsum", + TestSub: TestSubStruct{ + TestOptInt: nil, + testUnexposed: "unexposed", + TestUntaggedString: "untagged", + }, + TestOptionalSub: nil, + }, + } + + expectedRes := map[any]any{ + "data": map[any]any{ + "testString": "lorem ipsum", + "testSub": map[any]any{ + "testOptInt": nil, + "TestUntaggedString": "untagged", + }, + "testOptionalSub": nil, + }, + } + + path := ".data" + + model := merge.NewDefaultModelFromStruct(content, path, false) + + assert.Equal(t, expectedRes, model.Content()) +} + func TestDefaultModel_Content(t *testing.T) { content := map[any]any{ "data": map[any]any{ diff --git a/internal/project/project.go b/internal/project/project.go deleted file mode 100644 index 936330a17..000000000 --- a/internal/project/project.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package project - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - - "github.com/sirupsen/logrus" - - "github.com/sighupio/furyctl/internal/io" -) - -const ( - pathAlreadyExistsErr = "Directory already exists" - pathCreationErr = "Path dir couldn't be created. %v" - defaultDirPermission = 0o700 - defaultFilePermission = 0o600 -) - -// Project represents a simple structure with a couple of useful methods to init a project -type Project struct { - Path string -} - -// Reset deletes and recreates the (p.Path) base directory -func (p *Project) Reset() (err error) { - _, err = os.Stat(p.Path) - if !os.IsNotExist(err) { // Exists - logrus.Warnf("Removing %v directory", p.Path) - err = os.RemoveAll(p.Path) - if err != nil { - logrus.Errorf("Error removing the base dir %v. %v", p.Path, err) - return err - } - } - return nil -} - -// CreateSubDirs creates directories inside the p.Path base directory -func (p *Project) CreateSubDirs(subDirs []string) (err error) { - _, err = os.Stat(p.Path) - if !os.IsNotExist(err) { - logrus.Error(pathAlreadyExistsErr) - return errors.New(pathAlreadyExistsErr) - } - if os.IsNotExist(err) { - for _, subDir := range subDirs { - err = os.MkdirAll(fmt.Sprintf("%v/%v", p.Path, subDir), defaultDirPermission) - if err != nil { - logrus.Errorf(pathCreationErr, err) - return err - } - } - } - return nil -} - -// WriteFile writes a new file (fileName) with the content specified -func (p *Project) WriteFile(fileName string, content []byte) (err error) { - filePath := fmt.Sprintf("%v/%v", p.Path, fileName) - err = io.EnsureDir(filePath) - if err != nil { - return err - } - return ioutil.WriteFile(filePath, content, os.FileMode(defaultFilePermission)) -} - -// Check if the project directory exists. -// TODO improve the checks -func (p *Project) Check() error { - _, err := os.Stat(p.Path) - if os.IsNotExist(err) { - logrus.Errorf("Directory does not exists. %v", err) - return errors.New("Directory does not exists") - } - return nil -} diff --git a/internal/provisioners/provisioners.go b/internal/provisioners/provisioners.go deleted file mode 100644 index ed6c87eb2..000000000 --- a/internal/provisioners/provisioners.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package provisioners TODO -package provisioners - -import ( - "errors" - "fmt" - - "github.com/gobuffalo/packr/v2" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" - - "github.com/sighupio/furyctl/internal/bootstrap/provisioners/aws" - "github.com/sighupio/furyctl/internal/cluster/provisioners/eks" - "github.com/sighupio/furyctl/internal/configuration" -) - -// Provisioner represents a kubernetes terraform provisioner -type Provisioner interface { - InitMessage() string - UpdateMessage() string - DestroyMessage() string - - SetTerraformExecutor(tf *tfexec.Terraform) - TerraformExecutor() (tf *tfexec.Terraform) - TerraformFiles() []string - - Enterprise() bool - - Prepare() error - Plan() error - Update() (string, error) - Destroy() error - - Box() *packr.Box -} - -// Get returns an initialized provisioner -func Get(config configuration.Configuration) (Provisioner, error) { - switch { - case config.Kind == "Cluster": - return getClusterProvisioner(config) - case config.Kind == "Bootstrap": - return getBootstrapProvisioner(config) - default: - logrus.Errorf("Kind %v not found", config.Kind) - return nil, fmt.Errorf("kind %v not found", config.Kind) - } -} - -func getClusterProvisioner(config configuration.Configuration) (Provisioner, error) { - switch { - case config.Provisioner == "eks": - return eks.New(&config), nil - default: - logrus.Error("Provisioner not found") - return nil, errors.New("Provisioner not found") - } -} - -func getBootstrapProvisioner(config configuration.Configuration) (Provisioner, error) { - switch { - case config.Provisioner == "aws": - return aws.New(&config), nil - default: - logrus.Error("Provisioner not found") - return nil, errors.New("Provisioner not found") - } -} diff --git a/internal/schema/santhosh/loader.go b/internal/schema/santhosh/loader.go index 8d7dd3df8..be0ca9a8a 100644 --- a/internal/schema/santhosh/loader.go +++ b/internal/schema/santhosh/loader.go @@ -10,7 +10,7 @@ import ( "fmt" "os" - "github.com/santhosh-tekuri/jsonschema" + "github.com/santhosh-tekuri/jsonschema/v5" ) var ErrCannotLoadSchema = errors.New("failed to load schema file") diff --git a/internal/schema/santhosh/loader_test.go b/internal/schema/santhosh/loader_test.go new file mode 100644 index 000000000..f76ef9551 --- /dev/null +++ b/internal/schema/santhosh/loader_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package santhosh_test + +import ( + "errors" + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/schema/santhosh" +) + +func TestLoadSchema(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + schemaPath string + wantErr bool + wantErrMsg string + }{ + { + desc: "not existing schema", + schemaPath: "not-existing-schema.json", + wantErr: true, + wantErrMsg: "no such file or directory", + }, + { + desc: "broken schema", + schemaPath: "../../../test/data/integration/schema/santhosh/test-cluster-broken.json", + wantErr: true, + wantErrMsg: "jsonschema: invalid json ../../../test/data/integration/schema/santhosh/test-cluster-broken.json: unexpected EOF", + }, + { + desc: "wrong schema", + schemaPath: "../../../test/data/integration/schema/santhosh/test-cluster-wrong.json", + wantErr: true, + wantErrMsg: "compilation failed", + }, + { + desc: "minimal correct schema", + schemaPath: "../../../test/data/integration/schema/santhosh/test-cluster-correct.json", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + s, err := santhosh.LoadSchema(tC.schemaPath) + + if !tC.wantErr && err != nil { + t.Errorf("want no error, got %v", err) + } + + if tC.wantErr && err == nil { + t.Errorf("want error, got none") + } + + if tC.wantErr && err != nil && !errors.Is(err, santhosh.ErrCannotLoadSchema) { + t.Errorf("want error %v, got %v", santhosh.ErrCannotLoadSchema, err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("want error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + + if !tC.wantErr && s == nil { + t.Errorf("want schema, got nil") + } + }) + } +} diff --git a/internal/semver/compare.go b/internal/semver/compare.go index 2a06a043a..aaeb73e3a 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -13,11 +13,13 @@ import ( var ( // Link: https://regex101.com/r/Ly7O1x/3/ + //nolint:lll //We can't wrap regex regex = regexp.MustCompile(`^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) ErrInvalidSemver = fmt.Errorf("invalid semantic version") ) +// NewVersion takes a string and returns a Version. func NewVersion(v string) (Version, error) { if !isValid(v) { return "", fmt.Errorf("%w: %s", ErrInvalidSemver, v) @@ -32,7 +34,12 @@ func (s Version) String() string { return string(s) } -// SamePatch takes two versions and tell if they are part of the same patch +// SamePatchStr takes two version strings and tell if they match down to patch level. +func SamePatchStr(a, b string) bool { + return SamePatch(Version(a), Version(b)) +} + +// SamePatch takes two versions and tell if they are part of the same patch. func SamePatch(a, b Version) bool { if a == b { return true @@ -44,7 +51,12 @@ func SamePatch(a, b Version) bool { return aMajor == bMajor && aMinor == bMinor && aPatch == bPatch } -// SameMinor takes two versions and tell if they are part of the same minor +// SameMinorStr takes two version strings and tell if they match down to minor level. +func SameMinorStr(a, b string) bool { + return SameMinor(Version(a), Version(b)) +} + +// SameMinor takes two versions and tell if they are part of the same minor. func SameMinor(a, b Version) bool { if a == b { return true @@ -56,7 +68,7 @@ func SameMinor(a, b Version) bool { return aMajor == bMajor && aMinor == bMinor } -// Gt returns true if a is greater than b +// Gt returns true if a is greater than b. func Gt(va, vb string) bool { if va == vb { return false @@ -92,28 +104,40 @@ func Gt(va, vb string) bool { return false } -// Parts returns the major, minor, patch and buil+prerelease parts of a version +// Parts returns the major, minor, patch and buil+prerelease parts of a version. func Parts(v string) (int, int, int, string) { - pv := EnsurePrefix(v, "v") + pv := EnsurePrefix(v) if !isValid(pv) { return 0, 0, 0, "" } - parts := strings.Split(EnsureNoPrefix(v, "v"), ".") + parts := strings.Split(EnsureNoPrefix(v), ".") ch := "-" m := strings.Index(v, "-") p := strings.Index(v, "+") + if (m == -1 && p > -1) || (m > -1 && p > -1 && p < m) { ch = "+" } patchParts := strings.Split(strings.Join(parts[2:], "."), ch) - major, _ := strconv.Atoi(parts[0]) - minor, _ := strconv.Atoi(parts[1]) - patch, _ := strconv.Atoi(patchParts[0]) + major, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, 0, "" + } + + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, 0, "" + } + + patch, err := strconv.Atoi(patchParts[0]) + if err != nil { + return 0, 0, 0, "" + } if len(patchParts) > 1 { return major, minor, patch, strings.Join(patchParts[1:], ch) diff --git a/internal/semver/compare_test.go b/internal/semver/compare_test.go index 1bfad991c..8849b504a 100644 --- a/internal/semver/compare_test.go +++ b/internal/semver/compare_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package semver_test import ( @@ -244,3 +246,111 @@ func TestParts(t *testing.T) { }) } } + +func TestSamePatchStr(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + a string + b string + want bool + }{ + { + desc: "same patch, different build using dash", + a: "v1.2.3-1", + b: "v1.2.3-2", + want: true, + }, + { + desc: "same patch, different build using plus", + a: "v1.2.3+b1", + b: "v1.2.3+b2", + want: true, + }, + { + desc: "same patch", + a: "v1.2.3", + b: "v1.2.3", + want: true, + }, + { + desc: "same minor", + a: "v1.2.3", + b: "v1.2.4", + want: false, + }, + { + desc: "same major", + a: "v1.2.3", + b: "v1.3.4", + want: false, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := semver.SamePatchStr(tC.a, tC.b) + if got != tC.want { + t.Errorf("want = %t, got = %t", tC.want, got) + } + }) + } +} + +func TesSameMinorStr(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + a string + b string + want bool + }{ + { + desc: "same patch, different build using dash", + a: "v1.2.3-1", + b: "v1.2.3-2", + want: true, + }, + { + desc: "same patch, different build using plus", + a: "v1.2.3+b1", + b: "v1.2.3+b2", + want: true, + }, + { + desc: "same patch", + a: "v1.2.3", + b: "v1.2.3", + want: true, + }, + { + desc: "same minor", + a: "v1.2.3", + b: "v1.2.4", + want: true, + }, + { + desc: "same major", + a: "v1.2.3", + b: "v1.3.4", + want: false, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := semver.SameMinorStr(tC.a, tC.b) + if got != tC.want { + t.Errorf("want = %t, got = %t", tC.want, got) + } + }) + } +} diff --git a/internal/semver/prefix.go b/internal/semver/process.go similarity index 63% rename from internal/semver/prefix.go rename to internal/semver/process.go index af8c22fbd..306bebf81 100644 --- a/internal/semver/prefix.go +++ b/internal/semver/process.go @@ -6,16 +6,26 @@ package semver import "strings" -func EnsurePrefix(version, prefix string) string { +const prefix = "v" + +func EnsurePrefix(version string) string { if !strings.HasPrefix(version, prefix) { return prefix + version } + return version } -func EnsureNoPrefix(version, prefix string) string { +func EnsureNoPrefix(version string) string { if strings.HasPrefix(version, prefix) { return strings.TrimPrefix(version, prefix) } + return version } + +func EnsureNoBuild(version string) string { + v := strings.Split(version, "+")[0] + + return strings.Split(v, "-")[0] +} diff --git a/internal/semver/process_test.go b/internal/semver/process_test.go new file mode 100644 index 000000000..d4c70ffa0 --- /dev/null +++ b/internal/semver/process_test.go @@ -0,0 +1,115 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package semver_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/semver" +) + +func TestEnsurePrefix(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + version string + want string + }{ + { + desc: "has prefix", + version: "v1.2.3", + want: "v1.2.3", + }, + { + desc: "has no prefix", + version: "1.2.3", + want: "v1.2.3", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := semver.EnsurePrefix(tC.version) + if tC.want != got { + t.Errorf("want %q, got %q", tC.want, got) + } + }) + } +} + +func TestEnsureNoPrefix(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + version string + want string + }{ + { + desc: "has prefix", + version: "v1.2.3", + want: "1.2.3", + }, + { + desc: "has no prefix", + version: "1.2.3", + want: "1.2.3", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := semver.EnsureNoPrefix(tC.version) + if tC.want != got { + t.Errorf("want %q, got %q", tC.want, got) + } + }) + } +} + +func TestEnsureNoBuild(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + version string + want string + }{ + { + desc: "has build using plus", + version: "v1.2.3+b1", + want: "v1.2.3", + }, + { + desc: "has build using dash", + version: "v1.2.3-1", + want: "v1.2.3", + }, + { + desc: "has no build", + version: "1.2.3", + want: "1.2.3", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := semver.EnsureNoBuild(tC.version) + if tC.want != got { + t.Errorf("want %q, got %q", tC.want, got) + } + }) + } +} diff --git a/internal/template/config.go b/internal/template/config.go index 229b4bc75..454cd8e1d 100644 --- a/internal/template/config.go +++ b/internal/template/config.go @@ -4,6 +4,22 @@ package template +import ( + "errors" + "fmt" + "reflect" + + "github.com/sighupio/furyctl/internal/merge" + mapx "github.com/sighupio/furyctl/internal/x/map" +) + +var ( + ErrTemplatesNotFound = errors.New("templates key not found in template source custom") + ErrTemplateSourceCustomIsNil = errors.New("template source custom is nil") + ErrDataSourceBaseIsNil = errors.New("data source base is nil") + errCannotConvert = errors.New("error while converting") +) + type Templates struct { Includes []string `yaml:"includes,omitempty"` Excludes []string `yaml:"excludes,omitempty"` @@ -16,3 +32,121 @@ type Config struct { Include map[string]string `yaml:"include,omitempty"` Templates Templates `yaml:"templates,omitempty"` } + +func NewConfig(tplSource, data *merge.Merger, excluded []string) (Config, error) { + var cfg Config + + if *tplSource.GetCustom() == nil { + return cfg, ErrTemplateSourceCustomIsNil + } + + if *data.GetBase() == nil { + return cfg, ErrDataSourceBaseIsNil + } + + tmpl := Templates{} + + mergedTmpl, ok := (*tplSource.GetCustom()).Content()["templates"] + if ok { + tmplMap, err := newTemplatesFromMap(mergedTmpl) + if err != nil { + return cfg, err + } + + tmpl = *tmplMap + } + + tmpl.Excludes = append(tmpl.Excludes, excluded...) + + builder := mapx.NewBuilder(false) + + cfg.Templates = tmpl + cfg.Data = builder.ToMapStringAny((*data.GetBase()).Content()) + cfg.Include = nil + + return cfg, nil +} + +func newTemplatesFromMap(t any) (*Templates, error) { + var exc []string + + var inc []string + + var err error + + m, ok := t.(map[any]any) + if !ok { + return nil, fmt.Errorf("%w %v to map", errCannotConvert, t) + } + + incS, ok := m["includes"].([]any) + if !ok { + incS = nil + } + + inc, err = toTypeSlice[string](incS) + if err != nil { + return nil, err + } + + excS, ok := m["excludes"].([]any) + if !ok { + excS = nil + } + + exc, err = toTypeSlice[string](excS) + if err != nil { + return nil, err + } + + suf, err := toType[string](m["suffix"]) + if err != nil { + return nil, err + } + + pro, err := toType[bool](m["processFilename"]) + if err != nil { + return nil, err + } + + return &Templates{ + Includes: inc, + Excludes: exc, + Suffix: suf, + ProcessFilename: pro, + }, nil +} + +func toTypeSlice[T any](t []any) ([]T, error) { + s := make([]T, len(t)) + + if t == nil { + return s, nil + } + + for i, v := range t { + sV, err := toType[T](v) + if err != nil { + return s, err + } + + s[i] = sV + } + + return s, nil +} + +func toType[T any](t any) (T, error) { + var s T + + if t == nil { + return s, nil + } + + s, ok := t.(T) + if !ok { + return s, fmt.Errorf("%w to %s", errCannotConvert, reflect.TypeOf(s)) + } + + return s, nil +} diff --git a/internal/template/config_test.go b/internal/template/config_test.go new file mode 100644 index 000000000..0c1e0d934 --- /dev/null +++ b/internal/template/config_test.go @@ -0,0 +1,142 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template_test + +import ( + "reflect" + "testing" + + "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/template" +) + +func TestNewConfig(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + tplSource *merge.Merger + data *merge.Merger + excluded []string + want template.Config + wantErr bool + err error + }{ + { + desc: "should return an error if custom in merger is nil", + tplSource: &merge.Merger{}, + data: &merge.Merger{}, + excluded: []string{}, + want: template.Config{}, + wantErr: true, + err: template.ErrTemplateSourceCustomIsNil, + }, + { + desc: "should return an error if data source base is nil", + tplSource: merge.NewMerger( + merge.NewDefaultModel(map[any]any{ + "data": map[any]any{ + "test": map[any]any{ + "foo": "bar", + }, + }, + }, ".data"), + merge.NewDefaultModel(map[any]any{ + "templates": map[any]any{ + "template": map[any]any{ + "foo": "bar", + }, + }, + }, ".data"), + ), + data: &merge.Merger{}, + excluded: []string{}, + want: template.Config{}, + wantErr: true, + err: template.ErrDataSourceBaseIsNil, + }, + { + desc: "should return a config with the correct values", + tplSource: merge.NewMerger( + merge.NewDefaultModel(map[any]any{ + "data": map[any]any{ + "test": map[any]any{ + "foo": "bar", + }, + }, + }, ".data"), + merge.NewDefaultModel(map[any]any{ + "templates": map[any]any{ + "excludes": []any{ + "foo", "bar", + }, + }, + "data": map[any]any{ + "test": map[any]any{ + "foo2": "bar2", + }, + }, + }, ".data"), + ), + data: merge.NewMerger( + merge.NewDefaultModel(map[any]any{ + "parentTest": map[any]any{ + "test": map[any]any{ + "foo": "bar", + }, + }, + }, ".data"), + merge.NewDefaultModel(map[any]any{ + "parentTest": map[any]any{ + "test": map[any]any{ + "foo2": "bar2", + }, + }, + }, ".data"), + ), + excluded: []string{"baz", "ar"}, + want: template.Config{ + Templates: template.Templates{ + Includes: []string{}, + Excludes: []string{"foo", "bar", "baz", "ar"}, + }, + Include: nil, + Data: map[string]map[any]any{ + "parentTest": { + "test": map[any]any{ + "foo": "bar", + }, + }, + }, + }, + wantErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + got, err := template.NewConfig(tc.tplSource, tc.data, tc.excluded) + if err != nil { + if !tc.wantErr { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(err, tc.err) { + t.Fatalf("want error %v, got %v", tc.err, err) + } + } + + if err == nil && tc.wantErr { + t.Fatalf("expected error but got nil") + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("NewConfig() got = %v, want %v", got, tc.want) + } + }) + } +} diff --git a/internal/template/funcmap.go b/internal/template/funcmap.go index c88bd0a0f..51849899a 100644 --- a/internal/template/funcmap.go +++ b/internal/template/funcmap.go @@ -28,20 +28,45 @@ func (f *FuncMap) Delete(name string) { delete(f.FuncMap, name) } -func toYAML(v any) string { +func ToYAML(v any) string { + //nolint:errcheck // we don't care about the error here because we recover from it + defer func() { + _ = recover() + }() + data, err := yaml.Marshal(v) if err != nil { // Swallow errors inside of a template. return "" } + return strings.TrimSuffix(string(data), "\n") } -func fromYAML(str string) map[string]any { +func FromYAML(str string) map[string]any { m := map[string]any{} if err := yaml.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() } + return m } + +func HasKeyAny(m map[any]any, key any) bool { + v, ok := m[key] + if !ok { + return false + } + + if v == nil { + return false + } + + val, ok := v.(map[any]any) + if ok { + return len(val) > 0 + } + + return true +} diff --git a/internal/template/funcmap_test.go b/internal/template/funcmap_test.go index d2b0171f7..b96a1cdd2 100644 --- a/internal/template/funcmap_test.go +++ b/internal/template/funcmap_test.go @@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package template_test import ( "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/sighupio/furyctl/internal/template" @@ -39,3 +43,90 @@ func TestFuncMap_Delete(t *testing.T) { assert.Nil(t, f.FuncMap["test"]) } + +func TestToYAML(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + data any + want string + }{ + { + desc: "empty yaml", + want: "null", + }, + { + desc: "simple yaml", + data: map[string]string{ + "foo": "bar", + "baz": "quux", + }, + want: "baz: quux\nfoo: bar", + }, + { + desc: "broken yaml", + data: map[string]func(){ + "foo": func() {}, + }, + want: "", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := template.ToYAML(tC.data) + + if got != tC.want { + t.Fatalf("expected %q, got %q", tC.want, got) + } + }) + } +} + +func TestFromYAML(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + data string + want map[string]any + }{ + { + desc: "empty yaml", + data: "", + want: map[string]any{}, + }, + { + desc: "simple yaml", + data: "baz: quux\nfoo: bar", + want: map[string]any{ + "foo": "bar", + "baz": "quux", + }, + }, + { + desc: "broken yaml", + data: "baz:\n: quux\nfoo: bar", + want: map[string]any{ + "Error": "yaml: line 1: did not find expected key", + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + got := template.FromYAML(tC.data) + + if !cmp.Equal(got, tC.want, cmpopts.EquateEmpty()) { + t.Fatalf("expected %+v, got %+v", tC.want, got) + } + }) + } +} diff --git a/internal/template/generator.go b/internal/template/generator.go index a1a8ffbbc..6ad1bb69e 100644 --- a/internal/template/generator.go +++ b/internal/template/generator.go @@ -15,12 +15,11 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/furyctl/internal/io" + iox "github.com/sighupio/furyctl/internal/x/io" ) -var ErrProcessTemplate = errors.New("error processing template") - -type generator struct { +type Generator struct { + rootSrc string source string target string context map[string]map[any]any @@ -29,13 +28,15 @@ type generator struct { } func NewGenerator( + rootSrc, source, target string, context map[string]map[any]any, funcMap FuncMap, dryRun bool, -) *generator { - return &generator{ +) *Generator { + return &Generator{ + rootSrc: rootSrc, source: source, target: target, context: context, @@ -44,27 +45,38 @@ func NewGenerator( } } -func (g *generator) ProcessTemplate() (*template.Template, error) { - const helpersPath = "source/_helpers.tpl" +func (g *Generator) ProcessTemplate() (*template.Template, error) { + helpersPath := filepath.Join(g.rootSrc, "_helpers.tpl") _, err := os.Stat(helpersPath) - if err == nil { - return template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source, helpersPath) + tpl, err := template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source, helpersPath) + if err != nil { + return nil, fmt.Errorf("error processing template: %w", err) + } + + return tpl, nil } if errors.Is(err, os.ErrNotExist) { - logrus.Warnf("template helpers file '%s' not found\n", helpersPath) + tpl, err := template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source) + if err != nil { + return nil, fmt.Errorf("error processing template: %w", err) + } - return template.New(filepath.Base(g.source)).Funcs(g.funcMap.FuncMap).ParseFiles(g.source) + return tpl, nil } - return nil, fmt.Errorf("%w using helper '%s': %v", ErrProcessTemplate, helpersPath, err) + return nil, fmt.Errorf("error processing template using helper '%s': %w", helpersPath, err) } -func (g *generator) GetMissingKeys(tpl *template.Template) []string { +func (g *Generator) GetMissingKeys(tpl *template.Template) []string { var missingKeys []string + if tpl == nil || tpl.Tree == nil || tpl.Tree.Root == nil { + return missingKeys + } + node := NewNode() node.FromNodeList(tpl.Tree.Root.Nodes) @@ -78,7 +90,7 @@ func (g *generator) GetMissingKeys(tpl *template.Template) []string { return missingKeys } -func (g *generator) ProcessFile(tpl *template.Template) (bytes.Buffer, error) { +func (g *Generator) ProcessFile(tpl *template.Template) (bytes.Buffer, error) { var generatedContent bytes.Buffer if !g.dryRun { @@ -86,24 +98,28 @@ func (g *generator) ProcessFile(tpl *template.Template) (bytes.Buffer, error) { } err := tpl.Execute(&generatedContent, g.context) + if err != nil { + return generatedContent, fmt.Errorf("error processing template: %w", err) + } - return generatedContent, err + return generatedContent, nil } -func (g *generator) ProcessFilename( +func (g *Generator) ProcessFilename( tm *Model, ) (string, error) { var realTarget string - if tm.Config.Templates.ProcessFilename { // try to process filename as template + if tm.Config.Templates.ProcessFilename { // Try to process filename as template. tpl := template.Must( template.New("currentTarget").Funcs(g.funcMap.FuncMap).Parse(g.target)) destination := bytes.NewBufferString("") if err := tpl.Execute(destination, g.context); err != nil { - return "", err + return "", fmt.Errorf("error processing filename: %w", err) } + realTarget = destination.String() } else { realTarget = g.target @@ -111,17 +127,17 @@ func (g *generator) ProcessFilename( suf := tm.Suffix if strings.HasSuffix(realTarget, suf) { - realTarget = realTarget[:len(realTarget)-len(tm.Suffix)] // cut off extension (.tmpl) from the end + realTarget = realTarget[:len(realTarget)-len(tm.Suffix)] // Cut off extension (.tmpl) from the end. } return realTarget, nil } -func (g *generator) UpdateTarget(newTarget string) { +func (g *Generator) UpdateTarget(newTarget string) { g.target = newTarget } -func (g *generator) WriteMissingKeysToFile( +func (*Generator) WriteMissingKeysToFile( missingKeys []string, tmplPath, outputPath string, @@ -140,10 +156,15 @@ func (g *generator) WriteMissingKeysToFile( outLog := fmt.Sprintf("[%s]\n%s\n", tmplPath, strings.Join(missingKeys, "\n")) - return io.AppendBufferToFile(*bytes.NewBufferString(outLog), debugFilePath) + err := iox.AppendToFile(outLog, debugFilePath) + if err != nil { + return fmt.Errorf("error writing missing keys to log file: %w", err) + } + + return nil } -func (g *generator) getContextValueFromPath(path string) any { +func (g *Generator) getContextValueFromPath(path string) any { paths := strings.Split(path[1:], ".") if len(paths) == 0 { @@ -152,7 +173,7 @@ func (g *generator) getContextValueFromPath(path string) any { ret := g.context[paths[0]] - for _, key := range paths[1:] { + for i, key := range paths[1:] { mapAtKey, ok := ret[key] if !ok { return nil @@ -160,9 +181,17 @@ func (g *generator) getContextValueFromPath(path string) any { ret, ok = mapAtKey.(map[any]any) if !ok { - return mapAtKey + if i == len(paths)-2 { + return mapAtKey + } + + return nil } } + if len(ret) == 0 { + return nil + } + return ret } diff --git a/internal/template/generator_test.go b/internal/template/generator_test.go index ee674eed5..667782ab5 100644 --- a/internal/template/generator_test.go +++ b/internal/template/generator_test.go @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package template_test import ( "os" "testing" + gotemplate "text/template" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" @@ -169,3 +172,31 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_File(t *testing.T) { assert.Equal(t, expectedRes, string(result)) } + +func Test_Generator_GetMissingKeys(t *testing.T) { + path, err := os.MkdirTemp("", "test") + if err != nil { + t.Fatal(err) + } + + funcMap := template.NewFuncMap() + funcMap.Add("toYaml", template.ToYAML) + funcMap.Add("fromYaml", template.FromYAML) + + tg := template.NewGenerator( + path+"/source", + path+"/source", + path+"/target", + map[string]map[any]any{}, + funcMap, + true, + ) + + tpl := gotemplate.New("test") + tpl.Parse("{{.meta.name}}") + + missingKeys := tg.GetMissingKeys(tpl) + + assert.Equal(t, 1, len(missingKeys)) + assert.Equal(t, ".meta.name", missingKeys[0]) +} diff --git a/internal/template/mapper/mapper.go b/internal/template/mapper/mapper.go index 55f64d364..2ce166a9d 100644 --- a/internal/template/mapper/mapper.go +++ b/internal/template/mapper/mapper.go @@ -5,6 +5,8 @@ package mapper import ( + "errors" + "fmt" "os" "strings" ) @@ -14,6 +16,8 @@ const ( File = "file" ) +var errKeyIsNotAString = errors.New("key is not a string") + type Mapper struct { context map[string]map[any]any } @@ -37,7 +41,7 @@ func (m *Mapper) MapDynamicValues() (map[string]map[any]any, error) { return mappedCtx, nil } -func (m *Mapper) MapEnvironmentVars() map[any]any { +func (*Mapper) MapEnvironmentVars() map[any]any { envMap := make(map[any]any) for _, v := range os.Environ() { @@ -54,10 +58,14 @@ func injectDynamicRes( parentKey string, ) (map[any]any, error) { for k, v := range m { - spl := strings.Split(k.(string), "://") + key, ok := k.(string) + if !ok { + return nil, fmt.Errorf("%v %w", k, errKeyIsNotAString) + } - if len(spl) > 1 { + spl := strings.Split(key, "://") + if len(spl) > 1 { source := spl[0] sourceValue := spl[1] @@ -65,11 +73,13 @@ func injectDynamicRes( case Env: envVar := os.Getenv(sourceValue) parent[parentKey] = envVar + case File: content, err := readValueFromFile(sourceValue) if err != nil { return nil, err } + parent[parentKey] = content } diff --git a/internal/template/mapper/mapper_test.go b/internal/template/mapper/mapper_test.go index db90ed1e6..54429defc 100644 --- a/internal/template/mapper/mapper_test.go +++ b/internal/template/mapper/mapper_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package mapper_test import ( diff --git a/internal/template/model.go b/internal/template/model.go index 8364576b8..c6812a112 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -5,8 +5,8 @@ package template import ( + "errors" "fmt" - "io/ioutil" "os" "path/filepath" "regexp" @@ -14,9 +14,15 @@ import ( "gopkg.in/yaml.v2" - "github.com/sighupio/furyctl/internal/io" "github.com/sighupio/furyctl/internal/template/mapper" - yaml2 "github.com/sighupio/furyctl/internal/yaml" + iox "github.com/sighupio/furyctl/internal/x/io" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +var ( + errSourceMustbeSet = errors.New("source must be set") + errTargetMustbeSet = errors.New("target must be set") + errTemplateNotFound = errors.New("no template found") ) type Model struct { @@ -44,34 +50,28 @@ func NewTemplateModel( var model Config if len(source) < 1 { - return nil, fmt.Errorf("source must be set") + return nil, errSourceMustbeSet } if len(target) < 1 { - return nil, fmt.Errorf("target must be set") + return nil, errTargetMustbeSet } if len(configPath) > 0 { - readFile, err := ioutil.ReadFile(configPath) + readFile, err := os.ReadFile(configPath) if err != nil { - return nil, err + return nil, fmt.Errorf("error reading config file: %w", err) } if err = yaml.Unmarshal(readFile, &model); err != nil { - return nil, err - } - } - - if stopIfNotEmpty { - err := io.CheckDirIsEmpty(target) - if err != nil { - return nil, err + return nil, fmt.Errorf("error parsing config file: %w", err) } } funcMap := NewFuncMap() - funcMap.Add("toYaml", toYAML) - funcMap.Add("fromYaml", fromYAML) + funcMap.Add("toYaml", ToYAML) + funcMap.Add("fromYaml", FromYAML) + funcMap.Add("hasKeyAny", HasKeyAny) return &Model{ SourcePath: source, @@ -93,13 +93,21 @@ func (tm *Model) isExcluded(source string) bool { return true } } + return false } func (tm *Model) Generate() error { + if tm.StopIfTargetNotEmpty { + err := iox.CheckDirIsEmpty(tm.TargetPath) + if err != nil { + return fmt.Errorf("target directory is not empty: %w", err) + } + } + osErr := os.MkdirAll(tm.TargetPath, os.ModePerm) if osErr != nil { - return osErr + return fmt.Errorf("error creating target directory: %w", osErr) } context, cErr := tm.generateContext() @@ -111,12 +119,17 @@ func (tm *Model) Generate() error { context, err := ctxMapper.MapDynamicValues() if err != nil { - return err + return fmt.Errorf("error mapping dynamic values: %w", err) } tm.Context = context - return filepath.Walk(tm.SourcePath, tm.applyTemplates) + err = filepath.Walk(tm.SourcePath, tm.applyTemplates) + if err != nil { + return fmt.Errorf("error applying templates: %w", err) + } + + return nil } func (tm *Model) applyTemplates( @@ -128,18 +141,23 @@ func (tm *Model) applyTemplates( return err } + if info == nil { + return err + } + if info.IsDir() { return err } rel, err := filepath.Rel(tm.SourcePath, relSource) if err != nil { - return err + return fmt.Errorf("error getting relative path: %w", err) } currentTarget := filepath.Join(tm.TargetPath, rel) gen := NewGenerator( + tm.SourcePath, relSource, currentTarget, tm.Context, @@ -148,7 +166,7 @@ func (tm *Model) applyTemplates( ) realTarget, fErr := gen.ProcessFilename(tm) - if fErr != nil { // maybe we should fail back to real name instead? + if fErr != nil { // Maybe we should fail back to real name instead? return fErr } @@ -158,7 +176,7 @@ func (tm *Model) applyTemplates( if _, err := os.Stat(currentTargetDir); os.IsNotExist(err) { if err := os.MkdirAll(currentTargetDir, os.ModePerm); err != nil { - return err + return fmt.Errorf("error creating target directory: %w", err) } } @@ -169,7 +187,7 @@ func (tm *Model) applyTemplates( } if tmpl == nil { - return fmt.Errorf("no template found for %s", relSource) + return fmt.Errorf("%w for %s", errTemplateNotFound, relSource) } if tm.DryRun { @@ -183,15 +201,23 @@ func (tm *Model) applyTemplates( content, cErr := gen.ProcessFile(tmpl) if cErr != nil { - return fmt.Errorf("%+v filePath: %s", cErr, relSource) + return fmt.Errorf("%w filePath: %s", cErr, relSource) } - return io.CopyBufferToFile(content, relSource, realTarget) + err = iox.CopyBufferToFile(content, realTarget) + if err != nil { + return fmt.Errorf("error writing file: %w", err) + } + + return nil } - _, err = io.CopyFromSourceToTarget(relSource, realTarget) + err = iox.CopyFile(relSource, realTarget) + if err != nil { + return fmt.Errorf("error copying file: %w", err) + } - return err + return nil } func (tm *Model) generateContext() (map[string]map[any]any, error) { @@ -208,7 +234,7 @@ func (tm *Model) generateContext() (map[string]map[any]any, error) { cPath = v } - yamlConfig, err := yaml2.FromFileV2[map[any]any](cPath) + yamlConfig, err := yamlx.FromFileV2[map[any]any](cPath) if err != nil { return nil, err } diff --git a/internal/template/model_test.go b/internal/template/model_test.go index abe3ccf8b..68f38971f 100644 --- a/internal/template/model_test.go +++ b/internal/template/model_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package template_test import ( diff --git a/internal/template/node.go b/internal/template/node.go index 7c1a50667..06d809312 100644 --- a/internal/template/node.go +++ b/internal/template/node.go @@ -8,19 +8,9 @@ import ( "reflect" "strings" "text/template/parse" -) -// MapParseNodeToAlias is a map of parse.Node to its alias. -var MapParseNodeToAlias = map[parse.NodeType]interface{}{ - parse.NodeList: &ListNode{}, - parse.NodeRange: &RangeNode{}, - parse.NodePipe: &PipeNode{}, - parse.NodeTemplate: &TemplateNode{}, - parse.NodeIf: &IfNode{}, - parse.NodeAction: &ActionNode{}, - parse.NodeField: &FieldNode{}, - parse.NodeVariable: &VariableNode{}, -} + "github.com/sighupio/furyctl/internal/x/slices" +) type Node struct { Fields []string @@ -44,11 +34,23 @@ func (f *Node) FromNodeList(nodes []parse.Node) []string { } } - return f.Fields + return slices.Uniq(f.Fields) } func mapToAliasInterface(n parse.Node) interface{} { - t := MapParseNodeToAlias[n.Type()] + // MapParseNodeToAlias is a map of parse.Node to its alias. + mapParseNodeToAlias := map[parse.NodeType]interface{}{ + parse.NodeList: &ListNode{}, + parse.NodeRange: &RangeNode{}, + parse.NodePipe: &PipeNode{}, + parse.NodeTemplate: &TplNode{}, + parse.NodeIf: &IfNode{}, + parse.NodeAction: &ActionNode{}, + parse.NodeField: &FieldNode{}, + parse.NodeVariable: &VariableNode{}, + } + + t := mapParseNodeToAlias[n.Type()] if t == nil { return nil @@ -93,9 +95,9 @@ func (p *PipeNode) Set(n *Node) { } } -type TemplateNode parse.TemplateNode +type TplNode parse.TemplateNode -func (t *TemplateNode) Set(n *Node) { +func (t *TplNode) Set(n *Node) { if t.Pipe != nil { for _, cmd := range t.Pipe.Cmds { n.Set(n.FromNodeList(cmd.Args)) @@ -135,15 +137,15 @@ func (f *FieldNode) Set(n *Node) { type VariableNode parse.VariableNode -func (v *VariableNode) Set(n *Node) { - n.Set(append(n.Fields, stringsToPath(v.Ident))) +// Set Skips VariableNodes because they are not fields. +func (*VariableNode) Set(_ *Node) { + // Do nothing. } func stringsToPath(s []string) string { - var sb strings.Builder - for _, s := range s { - sb.WriteByte('.') - sb.WriteString(s) + if len(s) == 0 { + return "" } - return sb.String() + + return "." + strings.Join(s, ".") } diff --git a/internal/template/node_test.go b/internal/template/node_test.go index ddd438e9c..8c73ae077 100644 --- a/internal/template/node_test.go +++ b/internal/template/node_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package template_test import ( @@ -214,7 +216,7 @@ func TestVariableNode_Set(t *testing.T) { variableNodeSetter.Set(node) - assert.Equal(t, []string{".variable1"}, node.Fields) + assert.Equal(t, []string{}, node.Fields) } func TestRangeNode_Set(t *testing.T) { @@ -347,7 +349,7 @@ func TestTemplateNode_Set(t *testing.T) { }, } - tmplTemplateNode := reflect.ValueOf(templateNode).Convert(reflect.TypeOf(&template2.TemplateNode{})).Interface() + tmplTemplateNode := reflect.ValueOf(templateNode).Convert(reflect.TypeOf(&template2.TplNode{})).Interface() assert.NotNil(t, tmplTemplateNode) diff --git a/internal/terraform/install.go b/internal/terraform/install.go deleted file mode 100644 index b3ba0cdbd..000000000 --- a/internal/terraform/install.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package terraform - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/hashicorp/go-checkpoint" - "github.com/hashicorp/go-version" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/hashicorp/terraform-exec/tfinstall" - "github.com/sirupsen/logrus" - - "github.com/sighupio/furyctl/internal/io" -) - -// ensure ensures a working terraform version to be used in the project -func ensure(terraformVersion, terraformDownloadPath string) (binPath string, err error) { - if terraformVersion != "" { - logrus.Debugf("Installing terraform %v version", terraformVersion) - return install(terraformVersion, terraformDownloadPath) - } - logrus.Debug("Installing terraform latest version") - return installLatest(terraformDownloadPath) -} - -func alreadyAvailable(terraformVersion, terraformDownloadPath string) (bool, string) { - // validate version - v, err := version.NewVersion(terraformVersion) - if err != nil { - logrus.Warning(err) - return false, "" - } - expectedTerraformBinary := filepath.Join(terraformDownloadPath, "terraform") - binPath, err := tfinstall.Find(context.Background(), tfinstall.ExactPath(expectedTerraformBinary)) - if err != nil { - defer os.RemoveAll(expectedTerraformBinary) - defer os.RemoveAll(binPath) - logrus.Warning(err) - return false, "" - } - wd, err := ioutil.TempDir("", "tfexec") - if err != nil { - defer os.RemoveAll(expectedTerraformBinary) - defer os.RemoveAll(binPath) - logrus.Warning(err) - return false, "" - } - defer os.RemoveAll(wd) // Clean up - tf, err := tfexec.NewTerraform(wd, binPath) - if err != nil { - defer os.RemoveAll(expectedTerraformBinary) - defer os.RemoveAll(binPath) - logrus.Warning(err) - return false, "" - } - installedV, _, err := tf.Version(context.Background(), true) - if err != nil { - defer os.RemoveAll(expectedTerraformBinary) - defer os.RemoveAll(binPath) - logrus.Warning(err) - return false, "" - } - if !v.Equal(installedV) { - logrus.Warning("The installed version is different to the required version") - logrus.Debug("Removing old terraform version") - defer os.RemoveAll(expectedTerraformBinary) - defer os.RemoveAll(binPath) - return false, "" - } - logrus.Debugf("%s is up to date with the requested %s version", binPath, terraformVersion) - logrus.Info("terraform is up to date") - return true, binPath -} - -func install(terraformVersion, terraformDownloadPath string) (binPath string, err error) { - ready, binPath := alreadyAvailable(terraformVersion, terraformDownloadPath) - if !ready { - err := io.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) - if err != nil { - return "", err - } - binPath, err = tfinstall.Find(context.Background(), tfinstall.ExactVersion(terraformVersion, terraformDownloadPath)) - if err != nil { - logrus.Errorf("Error downloading version %v: %v", terraformVersion, err) - return "", err - } - } - return binPath, nil -} - -func installLatest(terraformDownloadPath string) (binPath string, err error) { - terraformVersion, err := latestVersion(true) - if err != nil { - return "", err - } - ready, binPath := alreadyAvailable(terraformVersion, terraformDownloadPath) - if !ready { - err := io.EnsureDir(filepath.Join(terraformDownloadPath, "terraform")) - if err != nil { - return "", err - } - binPath, err = tfinstall.Find(context.Background(), tfinstall.LatestVersion(terraformDownloadPath, false)) - if err != nil { - logrus.Errorf("Error downloading latest version: %v", err) - return "", err - } - } - return binPath, nil -} - -func latestVersion(forceCheckpoint bool) (string, error) { - resp, err := checkpoint.Check(&checkpoint.CheckParams{ - Product: "terraform", - Force: forceCheckpoint, - }) - if err != nil { - return "", err - } - - if resp.CurrentVersion == "" { - return "", fmt.Errorf("could not determine latest version of terraform using checkpoint: CHECKPOINT_DISABLE may be set") - } - - return resp.CurrentVersion, nil -} diff --git a/internal/terraform/terraform.go b/internal/terraform/terraform.go deleted file mode 100644 index e0dda8b5e..000000000 --- a/internal/terraform/terraform.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package terraform - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "strings" - - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/sirupsen/logrus" -) - -type Options struct { - Version string - - Backend string - BackendConfig map[string]string - ReconfigureBackend bool - UpgradeDeps bool - - WorkingDir string - ConfigDir string - - GitHubToken string - - LogDir string - Debug bool -} - -func NewExecutor(opts Options) (tf *tfexec.Terraform, err error) { - downloadPath := fmt.Sprintf("%v/bin", opts.WorkingDir) - tfBinary, err := ensure(opts.Version, downloadPath) - if err != nil { - return nil, err - } - tf, err = tfexec.NewTerraform(opts.WorkingDir, tfBinary) - if err != nil { - return nil, err - } - err = configureLogger(tf, opts.WorkingDir, opts.LogDir, opts.Debug) - if err != nil { - return nil, err - } - err = createBackendFile(opts.WorkingDir, opts.Backend, opts.BackendConfig) - if err != nil { - return nil, err - } - // Gets all os environment - netRcEnv := envMap(os.Environ()) - if opts.GitHubToken != "" { - err = configureGitHubNetrcAccess(opts.WorkingDir, opts.GitHubToken, opts.ConfigDir) - if err != nil { - return nil, err - } - // Adds/Override NETRC to use our own netrc file - netRcEnv["NETRC"] = fmt.Sprintf("%v/%v/.netrc", opts.WorkingDir, opts.ConfigDir) - } - // Set the env to the executor - err = tf.SetEnv(netRcEnv) - if err != nil { - return nil, err - } - return tf, err -} - -var forbidenTerraformEnvs = map[string]bool{ - "TF_LOG": true, - "TF_INPUT": true, - "TF_IN_AUTOMATION": true, - "TF_LOG_PATH": true, - "TF_REATTACH_PROVIDERS": true, - "TF_APPEND_USER_AGENT": true, - "TF_WORKSPACE": true, - "TF_DISABLE_PLUGIN_TLS": true, - "TF_SKIP_PROVIDER_VERIFY": true, -} - -const ( - varEnvVarPrefix = "TF_VAR_" -) - -func envMap(environ []string) map[string]string { - env := map[string]string{} - for _, ev := range environ { - parts := strings.SplitN(ev, "=", 2) - if len(parts) == 0 { - continue - } - k := parts[0] - v := "" - if len(parts) == 2 { - v = parts[1] - } - if !strings.HasPrefix(k, varEnvVarPrefix) && !forbidenTerraformEnvs[k] { - env[k] = v - } else { - logrus.Warnf("%v Environment variable discarted. Executor will not use it", k) - } - } - return env -} - -func configureLogger(tf *tfexec.Terraform, workingDir, logDir string, debug bool) (err error) { - logFile, err := os.Create(fmt.Sprintf("%v/%v/terraform.logs", workingDir, logDir)) - tf.SetLogger(logrus.StandardLogger()) - c := &tfwriter{ - logfile: logFile, - debug: debug, - } - if err != nil { - logrus.Errorf("Can not init log file. %v", err) - return err - } - tf.SetStdout(c) - tf.SetStderr(c) - return nil -} - -// configureGitHubNetrcAccess creates the .netrc file with the credentials to access github private repos -func configureGitHubNetrcAccess(path, token, configDir string) (err error) { - netrc := fmt.Sprintf(`machine github.com -login furyctl -password %v -`, token) - return ioutil.WriteFile(fmt.Sprintf("%v/%v/.netrc", path, configDir), []byte(netrc), os.FileMode(0o644)) -} - -// CreateBackendFile creates the backend.tf terraform file with the backend configuration chosen -func createBackendFile(path, backend string, backendConfig map[string]string) (err error) { - var backendFilebuffer bytes.Buffer - backendFilebuffer.WriteString(fmt.Sprintf(`terraform { - backend "%v" { -`, backend)) - for k, v := range backendConfig { - backendFilebuffer.WriteString(fmt.Sprintf(" %v = \"%v\"\n", k, v)) - } - backendFilebuffer.WriteString(` } -}`) - backendFileContent := backendFilebuffer.Bytes() - return ioutil.WriteFile(fmt.Sprintf("%v/backend.tf", path), backendFileContent, os.FileMode(0o644)) -} - -type tfwriter struct { - logfile *os.File - debug bool -} - -func (c *tfwriter) Write(data []byte) (n int, err error) { - n, err = c.logfile.Write(data) - if err != nil { - return 0, err - } - if c.debug { - fmt.Print(string(data)) - } - return n, nil -} diff --git a/internal/test/os.go b/internal/test/os.go new file mode 100644 index 000000000..f11fcf503 --- /dev/null +++ b/internal/test/os.go @@ -0,0 +1,21 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package test + +import ( + "os" + "testing" +) + +func MkdirTemp(t *testing.T) string { + t.Helper() + + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + t.Fatal(err) + } + + return dir +} diff --git a/internal/tool/ansible/runner.go b/internal/tool/ansible/runner.go new file mode 100644 index 000000000..329b03c32 --- /dev/null +++ b/internal/tool/ansible/runner.go @@ -0,0 +1,45 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ansible + +import ( + "fmt" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +type Paths struct { + Ansible string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Ansible +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Ansible, execx.CmdOptions{ + Args: []string{"--version"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting ansible version: %w", err) + } + + return out, nil +} diff --git a/internal/tool/ansible/runner_test.go b/internal/tool/ansible/runner_test.go new file mode 100644 index 000000000..e54aaebdb --- /dev/null +++ b/internal/tool/ansible/runner_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package ansible_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/tool/ansible" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := ansible.NewRunner(execx.NewFakeExecutor(), ansible.Paths{ + Ansible: "ansible", + WorkDir: os.TempDir(), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "ansible": + switch subcmd { + case "--version": + fmt.Fprintf(os.Stdout, "v1.2.3") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go new file mode 100644 index 000000000..67c75014e --- /dev/null +++ b/internal/tool/awscli/runner.go @@ -0,0 +1,45 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package awscli + +import ( + "fmt" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +type Paths struct { + Awscli string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Awscli +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ + Args: []string{"--version"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting awscli version: %w", err) + } + + return out, nil +} diff --git a/internal/tool/awscli/runner_test.go b/internal/tool/awscli/runner_test.go new file mode 100644 index 000000000..e668da973 --- /dev/null +++ b/internal/tool/awscli/runner_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package awscli_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/tool/awscli" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := awscli.NewRunner(execx.NewFakeExecutor(), awscli.Paths{ + Awscli: "aws", + WorkDir: os.TempDir(), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "aws": + switch subcmd { + case "--version": + fmt.Fprintf(os.Stdout, "v1.2.3") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go new file mode 100644 index 000000000..a6b710f9f --- /dev/null +++ b/internal/tool/furyagent/runner.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package furyagent + +import ( + "fmt" + "os" + "path" + + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +type Paths struct { + Furyagent string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Furyagent +} + +func (r *Runner) ConfigOpenvpnClient(name string) error { + cmd := execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ + Args: []string{"configure", "openvpn-client", fmt.Sprintf("--client-name=%s", name), "--config=furyagent.yml"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + if err := cmd.Run(); err != nil { + return fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) + } + + err := os.WriteFile(path.Join(r.paths.WorkDir, + fmt.Sprintf("%s.ovpn", name)), + cmd.Log.Out.Bytes(), iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing openvpn client config: %w", err) + } + + return nil +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ + Args: []string{"version"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting furyagent version: %w", err) + } + + return out, nil +} diff --git a/internal/tool/furyagent/runner_test.go b/internal/tool/furyagent/runner_test.go new file mode 100644 index 000000000..c3ac60c01 --- /dev/null +++ b/internal/tool/furyagent/runner_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package furyagent_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/sighupio/furyctl/internal/tool/furyagent" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := furyagent.NewRunner(execx.NewFakeExecutor(), furyagent.Paths{ + Furyagent: "furyagent", + WorkDir: os.TempDir(), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func Test_Runner_ConfigOpenvpnClient(t *testing.T) { + r := furyagent.NewRunner(execx.NewFakeExecutor(), furyagent.Paths{ + Furyagent: "furyagent", + WorkDir: os.TempDir(), + }) + + err := r.ConfigOpenvpnClient("furyctltest") + if err != nil { + t.Fatal(err) + } + + info, err := os.Stat(filepath.Join(os.TempDir(), "furyctltest.ovpn")) + if err != nil { + t.Fatal(err) + } + + if info.Size() == 0 { + t.Error("expected file to be not empty") + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "furyagent": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "v1.2.3") + case "configure": + fmt.Fprintf(os.Stdout, "fake openvpn client configuration") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go new file mode 100644 index 000000000..e510160ea --- /dev/null +++ b/internal/tool/kubectl/runner.go @@ -0,0 +1,165 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubectl + +import ( + "fmt" + "time" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +const ( + kubectlDeleteTimeout = 5 * time.Minute +) + +type Paths struct { + Kubectl string + WorkDir string + Kubeconfig string +} + +type Runner struct { + executor execx.Executor + paths Paths + serverSide bool + skipNotFound bool +} + +func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound bool) *Runner { + return &Runner{ + executor: executor, + paths: paths, + serverSide: serverSide, + skipNotFound: skipNotFound, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Kubectl +} + +func (r *Runner) Apply(manifestPath string, params ...string) error { + args := []string{"apply"} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + if r.serverSide { + args = append(args, "--server-side") + } + + if len(params) > 0 { + args = append(args, params...) + } + + args = append(args, "-f", manifestPath) + + _, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return fmt.Errorf("error applying manifests: %w", err) + } + + return nil +} + +func (r *Runner) Get(ns string, params ...string) (string, error) { + args := []string{"get"} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + if ns != "all" { + args = append(args, "-n", ns) + } else { + args = append(args, "-A") + } + + args = append(args, params...) + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return out, fmt.Errorf("error while getting resources: %w", err) + } + + return out, nil +} + +func (r *Runner) DeleteAllResources(res, ns string) (string, error) { + args := []string{"delete", res, "--all"} + + if ns != "all" { + args = append(args, "-n", ns) + } else { + args = append(args, "-A") + } + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return out, fmt.Errorf("error deleting all resources: %w", err) + } + + return out, nil +} + +func (r *Runner) Delete(manifestPath string, params ...string) error { + args := []string{"delete"} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + if r.skipNotFound { + args = append(args, "--ignore-not-found=true") + } + + if len(params) > 0 { + args = append(args, params...) + } + + args = append(args, "-f", manifestPath) + + _, err := execx.CombinedOutputWithTimeout(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }), kubectlDeleteTimeout) + if err != nil { + return fmt.Errorf("error deleting resources: %w", err) + } + + return nil +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: []string{"version", "--client"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting kubectl version: %w", err) + } + + return out, nil +} diff --git a/internal/tool/kubectl/runner_test.go b/internal/tool/kubectl/runner_test.go new file mode 100644 index 000000000..ef36a3f50 --- /dev/null +++ b/internal/tool/kubectl/runner_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package kubectl_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/tool/kubectl" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := kubectl.NewRunner(execx.NewFakeExecutor(), kubectl.Paths{ + Kubectl: "kubectl", + WorkDir: os.TempDir(), + }, true, true) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "kubectl": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "v1.2.3") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/kustomize/runner.go b/internal/tool/kustomize/runner.go new file mode 100644 index 000000000..6a403e39b --- /dev/null +++ b/internal/tool/kustomize/runner.go @@ -0,0 +1,60 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kustomize + +import ( + "fmt" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +type Paths struct { + Kustomize string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Kustomize +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kustomize, execx.CmdOptions{ + Args: []string{"version", "--short"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting kustomize version: %w", err) + } + + return out, nil +} + +func (r *Runner) Build() (string, error) { + args := []string{"build", "--load_restrictor", "none", "."} + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kustomize, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error while running kustomize build: %w", err) + } + + return out, nil +} diff --git a/internal/tool/kustomize/runner_test.go b/internal/tool/kustomize/runner_test.go new file mode 100644 index 000000000..736727af4 --- /dev/null +++ b/internal/tool/kustomize/runner_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package kustomize_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/tool/kustomize" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := kustomize.NewRunner(execx.NewFakeExecutor(), kustomize.Paths{ + Kustomize: "kustomize", + WorkDir: os.TempDir(), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "kustomize": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "v1.2.3") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/openvpn/runner.go b/internal/tool/openvpn/runner.go new file mode 100644 index 000000000..13081c0f2 --- /dev/null +++ b/internal/tool/openvpn/runner.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openvpn + +import ( + "fmt" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +type Paths struct { + Openvpn string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Openvpn +} + +func (r *Runner) Connect(name string) error { + err := execx.NewCmd("sudo", execx.CmdOptions{ + Args: []string{r.paths.Openvpn, "--config", fmt.Sprintf("%s.ovpn", name), "--daemon"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }).Run() + if err != nil { + return fmt.Errorf("error while running openvpn: %w", err) + } + + return nil +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Openvpn, execx.CmdOptions{ + Args: []string{"--version"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting openvpn version: %w", err) + } + + return out, nil +} diff --git a/internal/tool/openvpn/runner_test.go b/internal/tool/openvpn/runner_test.go new file mode 100644 index 000000000..8f4288625 --- /dev/null +++ b/internal/tool/openvpn/runner_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package openvpn_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/tool/openvpn" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := openvpn.NewRunner(execx.NewFakeExecutor(), openvpn.Paths{ + Openvpn: "openvpn", + WorkDir: os.TempDir(), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func Test_Runner_Connect(t *testing.T) { + r := openvpn.NewRunner(execx.NewFakeExecutor(), openvpn.Paths{ + Openvpn: "openvpn", + WorkDir: os.TempDir(), + }) + + if err := r.Connect("furyctltest"); err != nil { + t.Fatal(err) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "openvpn": + switch subcmd { + case "--version": + fmt.Fprintf(os.Stdout, "v1.2.3") + case "connect": + fmt.Fprintf(os.Stdout, "connected") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/runner.go b/internal/tool/runner.go new file mode 100644 index 000000000..c3cb45fcc --- /dev/null +++ b/internal/tool/runner.go @@ -0,0 +1,103 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tool + +import ( + "path/filepath" + + "github.com/sighupio/furyctl/internal/tool/ansible" + "github.com/sighupio/furyctl/internal/tool/awscli" + "github.com/sighupio/furyctl/internal/tool/furyagent" + "github.com/sighupio/furyctl/internal/tool/kubectl" + "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/openvpn" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +const ( + Ansible = "ansible" + Awscli = "awscli" + Furyagent = "furyagent" + Kubectl = "kubectl" + Kustomize = "kustomize" + Openvpn = "openvpn" + Terraform = "terraform" +) + +type Runner interface { + Version() (string, error) + CmdPath() string +} + +type RunnerFactoryPaths struct { + Bin string +} + +func NewRunnerFactory(executor execx.Executor, paths RunnerFactoryPaths) *RunnerFactory { + return &RunnerFactory{ + executor: executor, + paths: paths, + } +} + +type RunnerFactory struct { + executor execx.Executor + paths RunnerFactoryPaths +} + +func (rf *RunnerFactory) Create(name, version, workDir string) Runner { + if name == Ansible { + return ansible.NewRunner(rf.executor, ansible.Paths{ + Ansible: name, + WorkDir: workDir, + }) + } + + if name == Awscli { + return awscli.NewRunner(rf.executor, awscli.Paths{ + Awscli: "aws", + WorkDir: workDir, + }) + } + + if name == Furyagent { + return furyagent.NewRunner(rf.executor, furyagent.Paths{ + Furyagent: filepath.Join(rf.paths.Bin, name, version, name), + WorkDir: workDir, + }) + } + + if name == Kubectl { + return kubectl.NewRunner(rf.executor, kubectl.Paths{ + Kubectl: filepath.Join(rf.paths.Bin, name, version, name), + WorkDir: workDir, + }, + true, true) + } + + if name == Kustomize { + return kustomize.NewRunner(rf.executor, kustomize.Paths{ + Kustomize: filepath.Join(rf.paths.Bin, name, version, name), + WorkDir: workDir, + }) + } + + if name == Openvpn { + return openvpn.NewRunner(rf.executor, openvpn.Paths{ + Openvpn: filepath.Join(rf.paths.Bin, name, version, name), + WorkDir: workDir, + }) + } + + if name == Terraform { + return terraform.NewRunner(rf.executor, terraform.Paths{ + Terraform: filepath.Join(rf.paths.Bin, name, version, name), + WorkDir: workDir, + }) + } + + return nil +} diff --git a/internal/tool/runner_test.go b/internal/tool/runner_test.go new file mode 100644 index 000000000..2612f9833 --- /dev/null +++ b/internal/tool/runner_test.go @@ -0,0 +1,94 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tool_test + +import ( + "os" + "reflect" + "testing" + + "github.com/sighupio/furyctl/internal/tool" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_RunnerFactory_Create(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + tool string + wantRunner bool + wantRunnerType string + }{ + { + desc: "ansible", + tool: "ansible", + wantRunner: true, + wantRunnerType: "*ansible.Runner", + }, + { + desc: "furyagent", + tool: "furyagent", + wantRunner: true, + wantRunnerType: "*furyagent.Runner", + }, + { + desc: "kubectl", + tool: "kubectl", + wantRunner: true, + wantRunnerType: "*kubectl.Runner", + }, + { + desc: "kustomize", + tool: "kustomize", + wantRunner: true, + wantRunnerType: "*kustomize.Runner", + }, + { + desc: "openvpn", + tool: "openvpn", + wantRunner: true, + wantRunnerType: "*openvpn.Runner", + }, + { + desc: "terraform", + tool: "terraform", + wantRunner: true, + wantRunnerType: "*terraform.Runner", + }, + { + desc: "doesntexist", + tool: "doesntexist", + wantRunner: false, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + rf := tool.NewRunnerFactory(execx.NewFakeExecutor(), tool.RunnerFactoryPaths{ + Bin: os.TempDir(), + }) + + runner := rf.Create(tC.tool, "", os.TempDir()) + + if tC.wantRunner && runner == nil { + t.Errorf("expected a runner, got nil") + } + + if !tC.wantRunner && runner != nil { + t.Errorf("expected no runner, got %v", runner) + } + + if tC.wantRunner && reflect.TypeOf(runner).String() != tC.wantRunnerType { + t.Errorf("expected runner type '%s', got '%s'", tC.wantRunnerType, reflect.TypeOf(runner).String()) + } + }) + } +} diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go new file mode 100644 index 000000000..50d2c2e03 --- /dev/null +++ b/internal/tool/terraform/runner.go @@ -0,0 +1,165 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terraform + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path" + "regexp" + + tfjson "github.com/hashicorp/terraform-json" + + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +var errOutputFromApply = errors.New("can't get outputs from terraform apply logs") + +type OutputJSON struct { + Outputs map[string]*tfjson.StateOutput `json:"outputs"` +} + +type Paths struct { + Logs string + Outputs string + Plan string + Terraform string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Terraform +} + +func (r *Runner) Init() error { + err := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Args: []string{"init"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }).Run() + if err != nil { + return fmt.Errorf("command execution failed: %w", err) + } + + return nil +} + +func (r *Runner) Plan(timestamp int64, params ...string) error { + args := []string{"plan"} + + if len(params) > 0 { + args = append(args, params...) + } + + args = append(args, "-no-color", "-out", "plan/terraform.plan") + + cmd := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + if err := cmd.Run(); err != nil { + return fmt.Errorf("error running terraform plan: %w", err) + } + + err := os.WriteFile(path.Join(r.paths.Plan, + fmt.Sprintf("plan-%d.log", timestamp)), + cmd.Log.Out.Bytes(), + iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing terraform plan log: %w", err) + } + + return nil +} + +func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { + var oj OutputJSON + + cmd := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Args: []string{"apply", "-no-color", "-json", "plan/terraform.plan"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + if err := cmd.Run(); err != nil { + return oj, fmt.Errorf("cannot create cloud resources: %w", err) + } + + err := os.WriteFile(path.Join(r.paths.Logs, + fmt.Sprintf("%d.log", timestamp)), + cmd.Log.Out.Bytes(), + iox.FullRWPermAccess) + if err != nil { + return oj, fmt.Errorf("error writing terraform apply log: %w", err) + } + + parsedApplyLog, err := os.ReadFile(path.Join(r.paths.Logs, fmt.Sprintf("%d.log", timestamp))) + if err != nil { + return oj, fmt.Errorf("error reading terraform apply log: %w", err) + } + + applyLog := string(parsedApplyLog) + + pattern := regexp.MustCompile("(\"outputs\":){(.*?)}}") + + outputsStringIndex := pattern.FindStringIndex(applyLog) + if outputsStringIndex == nil { + return oj, errOutputFromApply + } + + outputsString := fmt.Sprintf("{%s}", applyLog[outputsStringIndex[0]:outputsStringIndex[1]]) + + if err := json.Unmarshal([]byte(outputsString), &oj); err != nil { + return oj, fmt.Errorf("error unmarshalling terraform apply outputs: %w", err) + } + + err = os.WriteFile(path.Join(r.paths.Outputs, "output.json"), []byte(outputsString), iox.FullRWPermAccess) + if err != nil { + return oj, fmt.Errorf("error writing terraform apply outputs: %w", err) + } + + return oj, nil +} + +func (r *Runner) Destroy() error { + err := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Args: []string{"destroy", "-auto-approve"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }).Run() + if err != nil { + return fmt.Errorf("error running terraform destroy: %w", err) + } + + return nil +} + +func (r *Runner) Version() (string, error) { + log, err := execx.CombinedOutput(execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Args: []string{"version"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error running terraform version: %w", err) + } + + return log, nil +} diff --git a/internal/tool/terraform/runner_test.go b/internal/tool/terraform/runner_test.go new file mode 100644 index 000000000..c549e9011 --- /dev/null +++ b/internal/tool/terraform/runner_test.go @@ -0,0 +1,147 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package terraform_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/sighupio/furyctl/internal/test" + "github.com/sighupio/furyctl/internal/tool/terraform" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Init(t *testing.T) { + r := terraform.NewRunner(execx.NewFakeExecutor(), terraform.Paths{ + Terraform: "terraform", + WorkDir: test.MkdirTemp(t), + }) + + if err := r.Init(); err != nil { + t.Fatal(err) + } +} + +func Test_Runner_Plan(t *testing.T) { + paths := terraform.Paths{ + Terraform: "terraform", + WorkDir: test.MkdirTemp(t), + Logs: test.MkdirTemp(t), + Outputs: test.MkdirTemp(t), + Plan: test.MkdirTemp(t), + } + + r := terraform.NewRunner(execx.NewFakeExecutor(), paths) + + if err := r.Plan(42); err != nil { + t.Fatal(err) + } + + info, err := os.Stat(filepath.Join(paths.Plan, "plan-42.log")) + if err != nil { + t.Fatal(err) + } + + if info.Size() == 0 { + t.Error("expected file to be not empty") + } +} + +func Test_Runner_Apply(t *testing.T) { + paths := terraform.Paths{ + Terraform: "terraform", + WorkDir: test.MkdirTemp(t), + Logs: test.MkdirTemp(t), + Outputs: test.MkdirTemp(t), + Plan: test.MkdirTemp(t), + } + + r := terraform.NewRunner(execx.NewFakeExecutor(), paths) + + if _, err := r.Apply(42); err != nil { + t.Fatal(err) + } + + info1, err := os.Stat(filepath.Join(paths.Logs, "42.log")) + if err != nil { + t.Fatal(err) + } + + if info1.Size() == 0 { + t.Error("expected '42.log' file to be not empty") + } + + info2, err := os.Stat(filepath.Join(paths.Outputs, "output.json")) + if err != nil { + t.Fatal(err) + } + + if info2.Size() == 0 { + t.Error("expected 'output.json' file to be not empty") + } + + out, err := os.ReadFile(filepath.Join(paths.Outputs, "output.json")) + if err != nil { + t.Fatal(err) + } + + want := `{"outputs":{"foo":{"sensitive":false,"value":"bar"}}}` + + if string(out) != want { + t.Errorf("expected output to be '%s', got '%s'", want, string(out)) + } +} + +func Test_Runner_Version(t *testing.T) { + r := terraform.NewRunner(execx.NewFakeExecutor(), terraform.Paths{ + Terraform: "terraform", + WorkDir: test.MkdirTemp(t), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "v1.2.3" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "terraform": + switch subcmd { + case "init": + fmt.Fprintf(os.Stdout, "initialized") + case "plan": + fmt.Fprintf(os.Stdout, "planned") + case "apply": + fmt.Fprintf(os.Stdout, `{"outputs":{"foo":{"sensitive":false,"value":"bar"}}}`) + case "version": + fmt.Fprintf(os.Stdout, "v1.2.3") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tools/furyagent.go b/internal/tools/furyagent.go deleted file mode 100644 index afde946fb..000000000 --- a/internal/tools/furyagent.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - - "github.com/sighupio/furyctl/internal/semver" -) - -func NewFuryAgent(version string) *FuryAgent { - return &FuryAgent{ - version: version, - os: runtime.GOOS, - arch: "amd64", - } -} - -type FuryAgent struct { - version string - os string - arch string -} - -func (f *FuryAgent) SrcPath() string { - return fmt.Sprintf( - "https://github.com/sighupio/furyagent/releases/download/%s/furyagent-%s-%s", - semver.EnsurePrefix(f.version, "v"), - f.os, - f.arch, - ) -} - -func (f *FuryAgent) Rename(basePath string) error { - oldName := fmt.Sprintf("furyagent-%s-%s", f.os, f.arch) - newName := "furyagent" - - return os.Rename(filepath.Join(basePath, oldName), filepath.Join(basePath, newName)) -} diff --git a/internal/tools/furyagent_test.go b/internal/tools/furyagent_test.go deleted file mode 100644 index f401f8fa0..000000000 --- a/internal/tools/furyagent_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools_test - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/sighupio/furyctl/internal/tools" -) - -func Test_FuryAgent_SrcPath(t *testing.T) { - wantSrcPath := fmt.Sprintf( - "https://github.com/sighupio/furyagent/releases/download/v0.3.0/furyagent-%s-amd64", - runtime.GOOS, - ) - - testCases := []struct { - desc string - version string - }{ - { - desc: "0.3.0", - version: "0.3.0", - }, - { - desc: "v0.3.0", - version: "v0.3.0", - }, - } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - fa := tools.NewFuryAgent(tC.version) - if fa.SrcPath() != wantSrcPath { - t.Errorf("Wrong furyagent src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) - } - }) - } -} - -func Test_FuryAgent_Rename(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-test-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - if _, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf("furyagent-%s-amd64", runtime.GOOS))); err != nil { - t.Fatalf("error creating temp file: %v", err) - } - - fa := tools.NewFuryAgent("0.3.0") - - if err := fa.Rename(tmpDir); err != nil { - t.Fatalf("Error renaming furyagent binary: %v", err) - } - - info, err := os.Stat(filepath.Join(tmpDir, "furyagent")) - if err != nil { - t.Fatalf("Error stating furyagent binary: %v", err) - } - - if info.IsDir() { - t.Errorf("furyagent binary is a directory") - } -} diff --git a/internal/tools/kubectl.go b/internal/tools/kubectl.go deleted file mode 100644 index 537792673..000000000 --- a/internal/tools/kubectl.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools - -import ( - "fmt" - "runtime" - - "github.com/sighupio/furyctl/internal/semver" -) - -func NewKubectl(version string) *Kubectl { - return &Kubectl{ - version: version, - os: runtime.GOOS, - arch: "amd64", - } -} - -type Kubectl struct { - version string - os string - arch string -} - -func (k *Kubectl) SrcPath() string { - return fmt.Sprintf( - "https://dl.k8s.io/release/%s/bin/%s/%s/kubectl", - semver.EnsurePrefix(k.version, "v"), - k.os, - k.arch, - ) -} - -func (f *Kubectl) Rename(basePath string) error { - return nil -} diff --git a/internal/tools/kubectl_test.go b/internal/tools/kubectl_test.go deleted file mode 100644 index fc504847b..000000000 --- a/internal/tools/kubectl_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools_test - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/sighupio/furyctl/internal/tools" -) - -func Test_Kubectl_SrcPath(t *testing.T) { - wantSrcPath := fmt.Sprintf("https://dl.k8s.io/release/v1.23.10/bin/%s/amd64/kubectl", runtime.GOOS) - - testCases := []struct { - desc string - version string - }{ - { - desc: "1.23.10", - version: "1.23.10", - }, - { - desc: "v1.23.10", - version: "v1.23.10", - }, - } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - fa := tools.NewKubectl(tC.version) - if fa.SrcPath() != wantSrcPath { - t.Errorf("Wrong kubectl src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) - } - }) - } -} - -func Test_Kubectl_Rename(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-test-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - if _, err := os.Create(filepath.Join(tmpDir, "kubectl")); err != nil { - t.Fatalf("error creating temp file: %v", err) - } - - fa := tools.NewKubectl("1.23.10") - - if err := fa.Rename(tmpDir); err != nil { - t.Fatalf("Error renaming kubectl binary: %v", err) - } - - info, err := os.Stat(filepath.Join(tmpDir, "kubectl")) - if err != nil { - t.Fatalf("Error stating kubectl binary: %v", err) - } - - if info.IsDir() { - t.Errorf("kubectl binary is a directory") - } -} diff --git a/internal/tools/kustomize.go b/internal/tools/kustomize.go deleted file mode 100644 index 5654ffcd3..000000000 --- a/internal/tools/kustomize.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools - -import ( - "fmt" - "runtime" - - "github.com/sighupio/furyctl/internal/semver" -) - -func NewKustomize(version string) *Kustomize { - return &Kustomize{ - version: version, - os: runtime.GOOS, - arch: "amd64", - } -} - -type Kustomize struct { - version string - os string - arch string -} - -func (k *Kustomize) SrcPath() string { - return fmt.Sprintf( - "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/%s/kustomize_%s_%s_%s.tar.gz", - semver.EnsurePrefix(k.version, "v"), - semver.EnsurePrefix(k.version, "v"), - k.os, - k.arch, - ) -} - -func (f *Kustomize) Rename(basePath string) error { - return nil -} diff --git a/internal/tools/kustomize_test.go b/internal/tools/kustomize_test.go deleted file mode 100644 index 316ff84f5..000000000 --- a/internal/tools/kustomize_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools_test - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/sighupio/furyctl/internal/tools" -) - -func Test_Kustomize_SrcPath(t *testing.T) { - wantSrcPath := fmt.Sprintf( - "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.10.0/kustomize_v3.10.0_%s_amd64.tar.gz", - runtime.GOOS, - ) - - testCases := []struct { - desc string - version string - }{ - { - desc: "3.10.0", - version: "3.10.0", - }, - { - desc: "v3.10.0", - version: "v3.10.0", - }, - } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - fa := tools.NewKustomize(tC.version) - if fa.SrcPath() != wantSrcPath { - t.Errorf("Wrong kustomize src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) - } - }) - } -} - -func Test_Kustomize_Rename(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-test-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - if _, err := os.Create(filepath.Join(tmpDir, "kustomize")); err != nil { - t.Fatalf("error creating temp file: %v", err) - } - - fa := tools.NewKustomize("3.10.0") - - if err := fa.Rename(tmpDir); err != nil { - t.Fatalf("Error renaming kustomize binary: %v", err) - } - - info, err := os.Stat(filepath.Join(tmpDir, "kustomize")) - if err != nil { - t.Fatalf("Error stating kustomize binary: %v", err) - } - - if info.IsDir() { - t.Errorf("kustomize binary is a directory") - } -} diff --git a/internal/tools/terraform.go b/internal/tools/terraform.go deleted file mode 100644 index fbd9ea383..000000000 --- a/internal/tools/terraform.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools - -import ( - "fmt" - "runtime" - - "github.com/sighupio/furyctl/internal/semver" -) - -func NewTerraform(version string) *Terraform { - return &Terraform{ - version: version, - os: runtime.GOOS, - arch: "amd64", - } -} - -type Terraform struct { - version string - os string - arch string -} - -func (k *Terraform) SrcPath() string { - return fmt.Sprintf( - "https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_%s.zip", - semver.EnsureNoPrefix(k.version, "v"), - semver.EnsureNoPrefix(k.version, "v"), - k.os, - k.arch, - ) -} - -func (f *Terraform) Rename(basePath string) error { - return nil -} diff --git a/internal/tools/terraform_test.go b/internal/tools/terraform_test.go deleted file mode 100644 index 071054325..000000000 --- a/internal/tools/terraform_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools_test - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/sighupio/furyctl/internal/tools" -) - -func Test_Terraform_SrcPath(t *testing.T) { - wantSrcPath := fmt.Sprintf( - "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_%s_amd64.zip", - runtime.GOOS, - ) - - testCases := []struct { - desc string - version string - }{ - { - desc: "1.2.9", - version: "1.2.9", - }, - { - desc: "v1.2.9", - version: "v1.2.9", - }, - } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - fa := tools.NewTerraform(tC.version) - if fa.SrcPath() != wantSrcPath { - t.Errorf("Wrong terraform src path: want = %s, got = %s", wantSrcPath, fa.SrcPath()) - } - }) - } -} - -func Test_Terraform_Rename(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "furyctl-test-") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - - if _, err := os.Create(filepath.Join(tmpDir, "terraform")); err != nil { - t.Fatalf("error creating temp file: %v", err) - } - - fa := tools.NewTerraform("1.2.9") - - if err := fa.Rename(tmpDir); err != nil { - t.Fatalf("Error renaming terraform binary: %v", err) - } - - info, err := os.Stat(filepath.Join(tmpDir, "terraform")) - if err != nil { - t.Fatalf("Error stating terraform binary: %v", err) - } - - if info.IsDir() { - t.Errorf("terraform binary is a directory") - } -} diff --git a/internal/tools/tool.go b/internal/tools/tool.go deleted file mode 100644 index 236f9de7c..000000000 --- a/internal/tools/tool.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools - -type Tool interface { - SrcPath() string - Rename(basePath string) error -} - -func NewFactory() *Factory { - return &Factory{} -} - -type Factory struct{} - -func (f *Factory) Create(name, version string) Tool { - if name == "furyagent" { - return NewFuryAgent(version) - } - if name == "kubectl" { - return NewKubectl(version) - } - if name == "kustomize" { - return NewKustomize(version) - } - if name == "terraform" { - return NewTerraform(version) - } - return nil -} diff --git a/internal/tools/tool_test.go b/internal/tools/tool_test.go deleted file mode 100644 index 9596eb982..000000000 --- a/internal/tools/tool_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools_test - -import ( - "testing" - - "github.com/sighupio/furyctl/internal/tools" -) - -func Test_Factory_Create(t *testing.T) { - testCases := []struct { - desc string - wantTool bool - }{ - { - desc: "furyagent", - wantTool: true, - }, - { - desc: "kubectl", - wantTool: true, - }, - { - desc: "kustomize", - wantTool: true, - }, - { - desc: "terraform", - wantTool: true, - }, - { - desc: "unsupported", - wantTool: false, - }, - } - for _, tC := range testCases { - f := tools.NewFactory() - t.Run(tC.desc, func(t *testing.T) { - tool := f.Create(tC.desc, "0.0.0") - if tool == nil && tC.wantTool { - t.Errorf("Expected tool %s, got nil", tC.desc) - } - if tool != nil && !tC.wantTool { - t.Errorf("Expected nil, got tool %s", tC.desc) - } - }) - } -} diff --git a/internal/x/cobra/cmd.go b/internal/x/cobra/cmd.go new file mode 100644 index 000000000..dc8796034 --- /dev/null +++ b/internal/x/cobra/cmd.go @@ -0,0 +1,20 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cobrax + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// GetFullname returns the hierarchy of the command and its parents. For example: " ...". +func GetFullname(c *cobra.Command) string { + if c.Parent() == nil || c.Parent().Name() == "furyctl" { + return c.Name() + } + + return fmt.Sprintf("%s %s", GetFullname(c.Parent()), c.Name()) +} diff --git a/internal/cobrax/flags.go b/internal/x/cobra/flags.go similarity index 100% rename from internal/cobrax/flags.go rename to internal/x/cobra/flags.go diff --git a/internal/cobrax/flags_test.go b/internal/x/cobra/flags_test.go similarity index 98% rename from internal/cobrax/flags_test.go rename to internal/x/cobra/flags_test.go index c1f7d2fe3..8fcb667e9 100644 --- a/internal/cobrax/flags_test.go +++ b/internal/x/cobra/flags_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package cobrax_test import ( @@ -9,7 +11,7 @@ import ( "github.com/spf13/cobra" - "github.com/sighupio/furyctl/internal/cobrax" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" ) func Test_Flag_Bool(t *testing.T) { diff --git a/internal/x/exec/cmd.go b/internal/x/exec/cmd.go new file mode 100644 index 000000000..a41695ce6 --- /dev/null +++ b/internal/x/exec/cmd.go @@ -0,0 +1,153 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package execx + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" +) + +var ( + Debug = false //nolint:gochecknoglobals // This variable is shared between all the command instances. + LogFile *os.File //nolint:gochecknoglobals // This variable is shared between all the command instances. + ErrCmdFailed = errors.New("command failed") + ErrCmdTimeout = errors.New("command timed out") +) + +func NewErrCmdFailed(name string, args []string, err error, res *CmdLog) error { + return fmt.Errorf("%s %s: %w - %v\n%s", name, strings.Join(args, " "), ErrCmdFailed, err, res) +} + +func NewCmd(name string, opts CmdOptions) *Cmd { + outLog := bytes.NewBufferString("") + errLog := bytes.NewBufferString("") + + outWriters := []io.Writer{outLog} + errWriters := []io.Writer{errLog} + + if LogFile != nil { + outWriters = append(outWriters, LogFile) + errWriters = append(errWriters, LogFile) + } + + if opts.Executor == nil { + opts.Executor = NewStdExecutor() + } + + if opts.Out != nil { + outWriters = append(outWriters, opts.Out) + } + + if opts.Err != nil { + errWriters = append(errWriters, opts.Err) + } + + if Debug || LogFile == nil { + outWriters = append(outWriters, os.Stdout) + errWriters = append(errWriters, os.Stderr) + } + + coreCmd := opts.Executor.Command(name, opts.Args...) + coreCmd.Stdout = io.MultiWriter(outWriters...) + coreCmd.Stderr = io.MultiWriter(errWriters...) + coreCmd.Dir = opts.WorkDir + + return &Cmd{ + Cmd: coreCmd, + Log: &CmdLog{ + Out: outLog, + Err: errLog, + }, + } +} + +type Cmd struct { + *exec.Cmd + Log *CmdLog +} + +func (c *Cmd) Run() error { + if err := c.Cmd.Run(); err != nil { + return NewErrCmdFailed(c.Path, c.Args, err, c.Log) + } + + return nil +} + +func (c *Cmd) RunWithTimeout(timeout time.Duration) error { + var cmdCtx *exec.Cmd + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + + defer cancel() + + if len(c.Cmd.Args) == 1 { + cmdCtx = exec.CommandContext(ctx, c.Cmd.Path) + } else { + args := c.Cmd.Args[1:] + + cmdCtx = exec.CommandContext(ctx, c.Cmd.Path, args...) + } + + cmdCtx.Dir = c.Cmd.Dir + cmdCtx.Stdout = c.Cmd.Stdout + cmdCtx.Stderr = c.Cmd.Stderr + + err := cmdCtx.Run() + + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return fmt.Errorf( + "%w after %s: %s %s", ErrCmdTimeout, timeout, c.Cmd.Path, strings.Join(c.Cmd.Args, " "), + ) + } + + if err != nil { + return NewErrCmdFailed(c.Cmd.Path, c.Cmd.Args, err, c.Log) + } + + return nil +} + +type CmdOptions struct { + Args []string + Err io.Writer + Executor Executor + Out io.Writer + WorkDir string +} + +type CmdLog struct { + Out *bytes.Buffer + Err *bytes.Buffer +} + +func (c CmdLog) String() string { + return fmt.Sprintf("out: %s\nerr: %s\n", c.Out, c.Err) +} + +func CombinedOutput(cmd *Cmd) (string, error) { + err := cmd.Run() + + trimOut := strings.Trim(cmd.Log.Out.String(), "\n") + trimErr := strings.Trim(cmd.Log.Err.String(), "\n") + + return strings.Trim(trimOut+"\n"+trimErr, "\n"), err +} + +func CombinedOutputWithTimeout(cmd *Cmd, timeout time.Duration) (string, error) { + err := cmd.RunWithTimeout(timeout) + + trimOut := strings.Trim(cmd.Log.Out.String(), "\n") + trimErr := strings.Trim(cmd.Log.Err.String(), "\n") + + return strings.Trim(trimOut+"\n"+trimErr, "\n"), err +} diff --git a/internal/x/exec/cmd_test.go b/internal/x/exec/cmd_test.go new file mode 100644 index 000000000..1e9caa44a --- /dev/null +++ b/internal/x/exec/cmd_test.go @@ -0,0 +1,206 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package execx_test + +import ( + "bytes" + "errors" + "os" + "testing" + + "golang.org/x/exp/slices" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func TestNewErrCmdFailed(t *testing.T) { + err := execx.NewErrCmdFailed("foo", []string{"bar", "baz"}, errors.New("test error"), nil) + + if err == nil { + t.Error("error is nil") + } + + wantErr := "foo bar baz: command failed - test error\n" + if err.Error() != wantErr { + t.Errorf("wantErr = %s, got = %s", wantErr, err.Error()) + } +} + +func TestNewCmd(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + opts execx.CmdOptions + wantArgs []string + wantWorkDir bool + }{ + { + desc: "empty options", + }, + { + desc: "full options", + opts: execx.CmdOptions{ + Args: []string{"foo", "bar"}, + Err: bytes.NewBufferString("bar"), + Executor: execx.NewFakeExecutor(), + Out: bytes.NewBufferString("foo"), + WorkDir: os.TempDir(), + }, + wantArgs: []string{"foo", "bar"}, + wantWorkDir: true, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + cmd := execx.NewCmd("echo", tC.opts) + + if cmd == nil { + t.Fatal("cmd is nil") + } + + if cmd.Path == "" { + t.Errorf("cmd.Path is empty") + } + + for _, wantArg := range tC.wantArgs { + if !slices.Contains(cmd.Args, wantArg) { + t.Errorf("cmd.Args should contain %v but it does not: %v", wantArg, cmd.Args) + } + } + + if cmd.Log == nil { + t.Error("cmd.Log is nil") + } + + if (tC.wantWorkDir != false) && cmd.Dir == "" { + t.Errorf("cmd.Dir = %s, got = %s", "", cmd.Dir) + } + }) + } +} + +func Test_Cmd_Run(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + cmd *execx.Cmd + wantErr bool + }{ + { + desc: "failed run", + cmd: execx.NewCmd("false", execx.CmdOptions{}), + wantErr: true, + }, + { + desc: "succesful run", + cmd: execx.NewCmd("true", execx.CmdOptions{}), + wantErr: false, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + err := tC.cmd.Run() + + if (err != nil) != tC.wantErr { + t.Errorf("Cmd.Run() error = %v, wantErr = %v", err, tC.wantErr) + } + + if tC.wantErr && !errors.Is(err, execx.ErrCmdFailed) { + t.Errorf("Cmd.Err = %v, want = %v", tC.cmd.Err, execx.ErrCmdFailed) + } + }) + } +} + +func Test_CmdLog_String(t *testing.T) { + cmdLog := &execx.CmdLog{ + Out: bytes.NewBufferString("foo"), + Err: bytes.NewBufferString("bar"), + } + + want := "out: foo\nerr: bar\n" + if cmdLog.String() != want { + t.Errorf("want = %s, got = %s", want, cmdLog.String()) + } +} + +func TestCombinedOutput(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + want string + setup func() *execx.Cmd + }{ + { + desc: "no out, no err", + want: "", + setup: func() *execx.Cmd { + return execx.NewCmd("echo", execx.CmdOptions{}) + }, + }, + { + desc: "out, no err", + want: "foo", + setup: func() *execx.Cmd { + return execx.NewCmd("echo", execx.CmdOptions{ + Args: []string{"foo"}, + }) + }, + }, + { + desc: "no out, err", + want: "bar", + setup: func() *execx.Cmd { + cmd := execx.NewCmd("echo", execx.CmdOptions{}) + + cmd.Log.Err.WriteString("bar") + + return cmd + }, + }, + { + desc: "out, err", + want: "foo\nbar", + setup: func() *execx.Cmd { + cmd := execx.NewCmd("echo", execx.CmdOptions{ + Args: []string{"foo"}, + }) + + cmd.Log.Err.WriteString("bar") + + return cmd + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + ret, err := execx.CombinedOutput(tC.setup()) + if err != nil { + t.Fatal(err) + } + + if string(ret) != tC.want { + t.Errorf("want = %s, got = %s", tC.want, string(ret)) + } + }) + } +} diff --git a/internal/execx/exec.go b/internal/x/exec/executor.go similarity index 82% rename from internal/execx/exec.go rename to internal/x/exec/executor.go index bb3414138..85c0f2be2 100644 --- a/internal/execx/exec.go +++ b/internal/x/exec/executor.go @@ -20,7 +20,7 @@ func NewStdExecutor() *StdExecutor { type StdExecutor struct{} -func (e *StdExecutor) Command(name string, arg ...string) *exec.Cmd { +func (*StdExecutor) Command(name string, arg ...string) *exec.Cmd { return exec.Command(name, arg...) } @@ -30,7 +30,7 @@ func NewFakeExecutor() *FakeExecutor { type FakeExecutor struct{} -func (e *FakeExecutor) Command(name string, arg ...string) *exec.Cmd { +func (*FakeExecutor) Command(name string, arg ...string) *exec.Cmd { cs := []string{"-test.run=TestHelperProcess", "--", filepath.Base(name)} cs = append(cs, arg...) diff --git a/internal/x/exec/executor_test.go b/internal/x/exec/executor_test.go new file mode 100644 index 000000000..343e70564 --- /dev/null +++ b/internal/x/exec/executor_test.go @@ -0,0 +1,72 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package execx_test + +import ( + "fmt" + "os" + "testing" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_StdExecutor_Command(t *testing.T) { + e := execx.NewStdExecutor() + + cmd := e.Command("echo", "hello go world") + if cmd == nil { + t.Fatalf("expected command to be not nil") + } + + out, err := cmd.Output() + if err != nil { + t.Fatalf("expected command to be executed without errors: %v", err) + } + + if string(out) != "hello go world\n" { + t.Errorf("want = 'hello go world', got = '%s'", string(out)) + } +} + +func Test_FakeExecutor_Command(t *testing.T) { + e := execx.NewFakeExecutor() + + cmd := e.Command("fakectl", "hello world") + if cmd == nil { + t.Fatalf("expected command to be not nil") + } + + out, err := cmd.Output() + if err != nil { + t.Fatalf("expected command to be executed without errors: %v", err) + } + + t.Log(cmd.Args) + + if string(out) != "hello world" { + t.Errorf("want = 'hello go world', got = '%s'", string(out)) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, _ := args[3], args[4:] + + switch cmd { + case "fakectl": + fmt.Fprintf(os.Stdout, "hello world") + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} diff --git a/internal/x/io/fs.go b/internal/x/io/fs.go new file mode 100644 index 000000000..a1bab9188 --- /dev/null +++ b/internal/x/io/fs.go @@ -0,0 +1,187 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package iox + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "strings" +) + +const ( + FullPermAccess = 0o755 + UserGroupPerm = 0o750 + FullRWPermAccess = 0o600 + RWPermAccess = 0o644 +) + +var ( + ErrEmptyFile = errors.New("trimming buffer resulted in an empty file") + errTargetDirNotEmpty = errors.New("the target directory is not empty") + errNotRegularFile = errors.New("is not a regular file") +) + +func CheckDirIsEmpty(target string) error { + if _, err := os.Stat(target); os.IsNotExist(err) { + return nil + } + + err := filepath.Walk(target, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("the target directory is not empty, error while checking %s: %w", path, err) + } + + if target == path { + return nil + } + + return fmt.Errorf("%w: %s", errTargetDirNotEmpty, path) + }) + if err != nil { + return fmt.Errorf("error while checking path %s: %w", target, err) + } + + return nil +} + +func WriteFile(target string, data []byte) error { + if err := os.WriteFile(target, data, FullRWPermAccess); err != nil { + return fmt.Errorf("error while writing file %s: %w", target, err) + } + + return nil +} + +func AppendToFile(s, target string) error { + destination, err := os.OpenFile(target, os.O_APPEND|os.O_CREATE|os.O_WRONLY, RWPermAccess) + if err != nil { + return fmt.Errorf("error while opening file %s: %w", target, err) + } + + defer destination.Close() + + _, err = destination.Write([]byte(s)) + if err != nil { + return fmt.Errorf("error while writing to file %s: %w", target, err) + } + + return nil +} + +func CopyBufferToFile(b bytes.Buffer, target string) error { + if strings.TrimSpace(b.String()) == "" { + return nil + } + + destination, err := os.Create(target) + if err != nil { + return fmt.Errorf("error while creating file %s: %w", target, err) + } + + defer destination.Close() + + _, err = b.WriteTo(destination) + if err != nil { + return fmt.Errorf("error while writing to file %s: %w", target, err) + } + + return nil +} + +func CopyFile(src, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return fmt.Errorf("error while getting file info %s: %w", src, err) + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s %w", src, errNotRegularFile) + } + + source, err := os.Open(src) + if err != nil { + return fmt.Errorf("error while opening file %s: %w", src, err) + } + + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return fmt.Errorf("error while creating file %s: %w", dst, err) + } + + defer destination.Close() + + _, err = io.Copy(destination, source) + if err != nil { + return fmt.Errorf("error while copying file %s to %s: %w", src, dst, err) + } + + return nil +} + +func CopyRecursive(src fs.FS, dest string) error { + stuff, err := fs.ReadDir(src, ".") + if err != nil { + return fmt.Errorf("error while reading directory %s: %w", src, err) + } + + for _, file := range stuff { + if file.IsDir() { + sub, err := fs.Sub(src, file.Name()) + if err != nil { + return fmt.Errorf("error while converting sub directory %s to fs.FS: %w", file.Name(), err) + } + + if err := os.Mkdir(path.Join(dest, file.Name()), FullPermAccess); err != nil && !os.IsExist(err) { + return fmt.Errorf("error while creating directory %s: %w", file.Name(), err) + } + + if err := CopyRecursive(sub, path.Join(dest, file.Name())); err != nil { + return err + } + + continue + } + + fileContent, err := fs.ReadFile(src, file.Name()) + if err != nil { + return fmt.Errorf("error while reading file %s: %w", file.Name(), err) + } + + if err := EnsureDir(path.Join(dest, file.Name())); err != nil { + return err + } + + if err := os.WriteFile(path.Join(dest, file.Name()), fileContent, RWPermAccess); err != nil { + return fmt.Errorf("error while writing file %s: %w", file.Name(), err) + } + } + + return nil +} + +// EnsureDir creates the directories to host the file. +// Example: hello/world.md will create the hello dir if it does not exist. +func EnsureDir(fileName string) error { + dirName := filepath.Dir(fileName) + if _, serr := os.Stat(dirName); serr != nil { + if !os.IsNotExist(serr) { + return fmt.Errorf("error while checking if directory %s exists: %w", dirName, serr) + } + + if err := os.MkdirAll(dirName, os.ModePerm); err != nil { + return fmt.Errorf("error while creating directory %s: %w", dirName, err) + } + } + + return nil +} diff --git a/internal/x/io/fs_test.go b/internal/x/io/fs_test.go new file mode 100644 index 000000000..ca075e3c8 --- /dev/null +++ b/internal/x/io/fs_test.go @@ -0,0 +1,428 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package iox_test + +import ( + "bytes" + "fmt" + "io/fs" + "math" + "os" + "path/filepath" + "testing" + "time" + + "golang.org/x/exp/rand" + + iox "github.com/sighupio/furyctl/internal/x/io" +) + +func TestCheckDirIsEmpty(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (string, error) + wantErr bool + }{ + { + desc: "directory does not exist", + setup: func() (string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", err + } + + return filepath.Join(dir, "does-not-exist"), nil + }, + }, + { + desc: "directory is empty", + setup: func() (string, error) { + return os.MkdirTemp("", "furyctl") + }, + }, + { + desc: "directory is not empty", + setup: func() (string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", err + } + + f, err := os.CreateTemp(dir, "furyctl") + defer f.Close() + + return dir, err + }, + wantErr: true, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + dir, err := tC.setup() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = iox.CheckDirIsEmpty(dir) + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err == nil { + t.Errorf("expected error, got none") + } + }) + } +} + +func TestAppendBufferToFile(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (string, error) + }{ + { + desc: "existing file", + setup: func() (string, error) { + f, err := os.CreateTemp("", "furyctl") + if err != nil { + return "", err + } + defer f.Close() + + return f.Name(), nil + }, + }, + { + desc: "not existing file", + setup: func() (string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", err + } + + return filepath.Join(dir, "does-not-exist"), nil + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + file, err := tC.setup() + if err != nil { + t.Fatalf("unexpected setup error: %v", err) + } + + err = iox.AppendToFile("foo", file) + if err != nil { + t.Fatalf("could not append string 'foo': %v", err) + } + err = iox.AppendToFile("bar", file) + if err != nil { + t.Fatalf("could not append string 'bar': %v", err) + } + + got, err := os.ReadFile(file) + if err != nil { + t.Fatalf("could not get file content: %v", err) + } + + want := "foobar" + if string(got) != want { + t.Errorf("expected %s, got %s", want, got) + } + }) + } +} + +func TestCopyBufferToFile(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + bufContent string + setup func() (string, error) + }{ + { + desc: "empty buffer", + bufContent: "", + setup: func() (string, error) { + f, err := os.CreateTemp("", "furyctl") + if err != nil { + return "", err + } + + return f.Name(), nil + }, + }, + { + desc: "filled buffer", + bufContent: "foo bar baz quux", + setup: func() (string, error) { + f, err := os.CreateTemp("", "furyctl") + if err != nil { + return "", err + } + + return f.Name(), nil + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + dst, err := tC.setup() + if err != nil { + t.Fatalf("unexpected setup error: %v", err) + } + + b := bytes.NewBufferString(tC.bufContent) + + if err := iox.CopyBufferToFile(*b, dst); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + dstContent, err := os.ReadFile(dst) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if string(dstContent) != tC.bufContent { + t.Errorf("expected %s, got %s", tC.bufContent, dstContent) + } + }) + } +} + +func TestCopyFile(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (string, string, error) + wantErr bool + }{ + { + desc: "existing file", + setup: func() (string, string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", "", err + } + + src := filepath.Join(dir, "src") + dst := filepath.Join(dir, "dst") + + f, err := os.Create(src) + if err != nil { + return "", "", err + } + defer f.Close() + + return src, dst, nil + }, + }, + { + desc: "not existing source file", + setup: func() (string, string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", "", err + } + + src := filepath.Join(dir, "not-existing", "src") + dst := filepath.Join(dir, "dst") + + return src, dst, nil + }, + wantErr: true, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + src, dst, err := tC.setup() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = iox.CopyFile(src, dst) + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err == nil { + t.Errorf("expected error, got none") + } + }) + } +} + +func TestCopyRecursive(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (fs.FS, string, error) + want map[string]string + }{ + { + desc: "dir containing other dirs and files", + setup: func() (fs.FS, string, error) { + src, err := os.MkdirTemp("", "furyctl") + if err != nil { + return nil, "", err + } + + if err := os.Mkdir(filepath.Join(src, "foo"), 0o755); err != nil { + return nil, "", err + } + + f1, err := os.Create(filepath.Join(src, "bar.txt")) + if err != nil { + return nil, "", err + } + defer f1.Close() + + f2, err := os.Create(filepath.Join(src, "foo", "bar.txt")) + if err != nil { + return nil, "", err + } + defer f2.Close() + + if err := os.Mkdir(filepath.Join(src, "foo", "bar"), 0o755); err != nil { + return nil, "", err + } + + f3, err := os.Create(filepath.Join(src, "foo", "bar", "baz.txt")) + if err != nil { + return nil, "", err + } + defer f3.Close() + + if err := os.Mkdir(filepath.Join(src, "foo", "bar", "baz"), 0o755); err != nil { + return nil, "", err + } + + dst := filepath.Join( + os.TempDir(), + fmt.Sprintf("furyctl-iox-copy-recursive-test-%d-%d", time.Now().Unix(), rand.Intn(math.MaxInt)), + ) + + return os.DirFS(src), dst, nil + }, + want: map[string]string{ + "foo": "dir", + "bar.txt": "file", + "foo/bar.txt": "file", + "foo/bar": "dir", + "foo/bar/baz.txt": "file", + "foo/bar/baz": "dir", + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + src, dst, err := tC.setup() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := iox.CopyRecursive(src, dst); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + for fname, ftype := range tC.want { + info, err := os.Stat(filepath.Join(dst, fname)) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if ftype == "dir" && !info.IsDir() { + t.Errorf("expected %s to be a directory", fname) + } + if ftype == "file" && info.IsDir() { + t.Errorf("expected %s to be a file", fname) + } + } + }) + } +} + +func TestEnsureDir(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (string, error) + }{ + { + desc: "directory does not exist", + setup: func() (string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", err + } + + return filepath.Join(dir, "does", "not", "exist"), nil + }, + }, + { + desc: "directory already exists", + setup: func() (string, error) { + return os.MkdirTemp("", "furyctl") + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + dir, err := tC.setup() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := iox.EnsureDir(filepath.Join(dir, "to-be-created.txt")); err != nil { + t.Errorf("expected no error, got %v", err) + } + + info, err := os.Stat(dir) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if !info.IsDir() { + t.Errorf("expected '%s' to be a directory", dir) + } + }) + } +} diff --git a/internal/io/nullwriter.go b/internal/x/io/nullwriter.go similarity index 79% rename from internal/io/nullwriter.go rename to internal/x/io/nullwriter.go index 55f8365ad..4d5d45ade 100644 --- a/internal/io/nullwriter.go +++ b/internal/x/io/nullwriter.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package io +package iox func NewNullWriter() *NullWriter { return &NullWriter{} @@ -10,6 +10,6 @@ func NewNullWriter() *NullWriter { type NullWriter struct{} -func (nw *NullWriter) Write(p []byte) (n int, err error) { +func (*NullWriter) Write(_ []byte) (int, error) { return 0, nil } diff --git a/internal/x/io/nullwriter_test.go b/internal/x/io/nullwriter_test.go new file mode 100644 index 000000000..416c107f3 --- /dev/null +++ b/internal/x/io/nullwriter_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package iox_test + +import ( + "testing" + + iox "github.com/sighupio/furyctl/internal/x/io" +) + +func Test_NullWriter_Write(t *testing.T) { + nw := iox.NewNullWriter() + + n, err := nw.Write([]byte("hello world")) + if err != nil { + t.Fatalf("expected to write without errors: %v", err) + } + + if n != 0 { + t.Errorf("want = 0, got = %d", n) + } +} diff --git a/internal/x/kube/config.go b/internal/x/kube/config.go new file mode 100644 index 000000000..adb2b943d --- /dev/null +++ b/internal/x/kube/config.go @@ -0,0 +1,61 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubex + +import ( + "fmt" + "os" + "path" + "path/filepath" + + iox "github.com/sighupio/furyctl/internal/x/io" +) + +func CreateConfig(data []byte, p string) (string, error) { + err := iox.WriteFile(path.Join(p, "kubeconfig"), data) + if err != nil { + return "", fmt.Errorf("error writing kubeconfig file: %w", err) + } + + return path.Join(p, "kubeconfig"), nil +} + +func SetConfigEnv(p string) error { + kubePath, err := filepath.Abs(p) + if err != nil { + return fmt.Errorf("error getting kubeconfig absolute path: %w", err) + } + + err = os.Setenv("KUBECONFIG", kubePath) + if err != nil { + return fmt.Errorf("error setting kubeconfig env: %w", err) + } + + return nil +} + +func CopyConfigToWorkDir(p string) error { + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current dir: %w", err) + } + + kubePath, err := filepath.Abs(p) + if err != nil { + return fmt.Errorf("error getting kubeconfig absolute path: %w", err) + } + + kubeconfig, err := os.ReadFile(kubePath) + if err != nil { + return fmt.Errorf("error reading kubeconfig file: %w", err) + } + + err = iox.WriteFile(path.Join(currentDir, "kubeconfig"), kubeconfig) + if err != nil { + return fmt.Errorf("error writing kubeconfig file: %w", err) + } + + return nil +} diff --git a/internal/x/kube/config_test.go b/internal/x/kube/config_test.go new file mode 100644 index 000000000..981a0319f --- /dev/null +++ b/internal/x/kube/config_test.go @@ -0,0 +1,128 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubex_test + +import ( + "os" + "path" + "testing" + + kubex "github.com/sighupio/furyctl/internal/x/kube" +) + +func TestConfig(t *testing.T) { + t.Parallel() + + dirPath, err := os.MkdirTemp("", "test-kube-config-") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(dirPath) + + data := []byte("test") + + p, err := kubex.CreateConfig(data, dirPath) + if err != nil { + t.Fatal(err) + } + + wantPath := path.Join(dirPath, "kubeconfig") + + if p != wantPath { + t.Fatalf("got %s, want %s", p, wantPath) + } + + _, err = os.Stat(p) + if err != nil { + t.Fatal(err) + } + + f, err := os.ReadFile(p) + if err != nil { + t.Fatal(err) + } + + if string(f) != string(data) { + t.Fatalf("got %s, want %s", string(f), string(data)) + } +} + +func TestSetConfigEnv(t *testing.T) { + t.Parallel() + + dirPath, err := os.MkdirTemp("", "test-kube-config-") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(dirPath) + + data := []byte("test") + + p, err := kubex.CreateConfig(data, dirPath) + if err != nil { + t.Fatal(err) + } + + err = kubex.SetConfigEnv(p) + if err != nil { + t.Fatal(err) + } + + wantPath := path.Join(dirPath, "kubeconfig") + + gotPath := os.Getenv("KUBECONFIG") + + if gotPath != wantPath { + t.Fatalf("got %s, want %s", gotPath, wantPath) + } +} + +func TestCopyConfigToWorkDir(t *testing.T) { + t.Parallel() + + dirPath, err := os.MkdirTemp("", "test-kube-config-") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(dirPath) + + data := []byte("test") + + p, err := kubex.CreateConfig(data, dirPath) + if err != nil { + t.Fatal(err) + } + + err = kubex.CopyConfigToWorkDir(p) + if err != nil { + t.Fatal(err) + } + + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + wantPath := path.Join(wd, "kubeconfig") + + defer os.Remove(wantPath) + + _, err = os.Stat(wantPath) + if err != nil { + t.Fatal(err) + } + + f, err := os.ReadFile(wantPath) + if err != nil { + t.Fatal(err) + } + + if string(f) != string(data) { + t.Fatalf("got %s, want %s", string(f), string(data)) + } +} diff --git a/internal/x/kube/secret.go b/internal/x/kube/secret.go new file mode 100644 index 000000000..8ab04654c --- /dev/null +++ b/internal/x/kube/secret.go @@ -0,0 +1,40 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubex + +import ( + "encoding/base64" + "fmt" + + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +func CreateSecret(data []byte, name, namespace string) ([]byte, error) { + secret := struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata map[string]interface{} `yaml:"metadata"` + Type string `yaml:"type"` + Data map[string]string `yaml:"data"` + }{ + APIVersion: "v1", + Kind: "Secret", + Metadata: map[string]interface{}{ + "name": name, + "namespace": namespace, + }, + Type: "Opaque", + Data: map[string]string{ + "config": base64.StdEncoding.EncodeToString(data), + }, + } + + data, err := yamlx.MarshalV3(secret) + if err != nil { + return []byte{}, fmt.Errorf("%w", err) + } + + return data, nil +} diff --git a/internal/x/kube/secret_test.go b/internal/x/kube/secret_test.go new file mode 100644 index 000000000..9b9f5bce2 --- /dev/null +++ b/internal/x/kube/secret_test.go @@ -0,0 +1,53 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubex_test + +import ( + "testing" + + kubex "github.com/sighupio/furyctl/internal/x/kube" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +func TestCreateSecret(t *testing.T) { + t.Parallel() + + name := "test" + namespace := "test" + config := "dGVzdA==" + + secret := struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata map[string]interface{} `yaml:"metadata"` + Type string `yaml:"type"` + Data map[string]string `yaml:"data"` + }{ + APIVersion: "v1", + Kind: "Secret", + Metadata: map[string]interface{}{ + "name": name, + "namespace": namespace, + }, + Type: "Opaque", + Data: map[string]string{ + "config": config, + }, + } + + want, err := yamlx.MarshalV3(secret) + if err != nil { + t.Fatal(err) + } + + got, err := kubex.CreateSecret([]byte("test"), name, namespace) + if err != nil { + t.Fatal(err) + } + + if string(got) != string(want) { + t.Fatalf("got %s, want %s", string(got), string(want)) + } +} diff --git a/internal/x/logrus/config.go b/internal/x/logrus/config.go new file mode 100644 index 000000000..3757e784d --- /dev/null +++ b/internal/x/logrus/config.go @@ -0,0 +1,79 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package logrusx + +import ( + "fmt" + "io" + "os" + + "github.com/sirupsen/logrus" +) + +type formatterHook struct { + Writer io.Writer + LogLevels []logrus.Level + Formatter logrus.Formatter +} + +func (hook *formatterHook) Fire(entry *logrus.Entry) error { + line, err := hook.Formatter.Format(entry) + if err != nil { + return fmt.Errorf("error while formatting log entry: %w", err) + } + + _, err = hook.Writer.Write(line) + if err != nil { + return fmt.Errorf("error while writing log entry: %w", err) + } + + return nil +} + +func (hook *formatterHook) Levels() []logrus.Level { + return hook.LogLevels +} + +func newFormatterHook(writer io.Writer, formatter logrus.Formatter, logLevels []logrus.Level) *formatterHook { + return &formatterHook{ + Writer: writer, + Formatter: formatter, + LogLevels: logLevels, + } +} + +func InitLog(logFile *os.File, debug bool) { //nolint:revive // debug is a boolean flag + logrus.SetOutput(io.Discard) + + stdLevels := []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + } + + if debug { + stdLevels = append(stdLevels, logrus.DebugLevel) + logrus.SetLevel(logrus.DebugLevel) + } + + outLog := os.Stdout + + if logFile != nil { + outLog = logFile + + stdOutHook := newFormatterHook(os.Stdout, &logrus.TextFormatter{ + DisableTimestamp: true, + ForceColors: true, + }, stdLevels) + + logrus.AddHook(stdOutHook) + } + + logFileHook := newFormatterHook(outLog, &logrus.JSONFormatter{}, logrus.AllLevels) + + logrus.AddHook(logFileHook) +} diff --git a/internal/x/map/build.go b/internal/x/map/build.go new file mode 100644 index 000000000..aa23a776b --- /dev/null +++ b/internal/x/map/build.go @@ -0,0 +1,96 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mapx + +import ( + "fmt" + "reflect" + "strings" +) + +type Builder struct { + skipEmpty bool +} + +func NewBuilder(skipEmpty bool) *Builder { + return &Builder{ + skipEmpty: skipEmpty, + } +} + +func (b *Builder) FromStruct(s any, tagType string) map[any]any { + if s == nil { + return nil + } + + out := make(map[any]any) + + sType := reflect.TypeOf(s) + + if sType.Kind() != reflect.Struct { + return nil + } + + sVal := reflect.ValueOf(s) + + for i := 0; i < sVal.NumField(); i++ { + if !sVal.Field(i).CanInterface() { + continue + } + + fieldName := sType.Field(i).Name + + if tagType != "" { + tag, ok := sType.Field(i).Tag.Lookup(tagType) + if ok { + tag = strings.Split(tag, ",")[0] + fieldName = tag + } + } + + val := sVal.Field(i) + + if val.Kind() == reflect.Ptr { + val = reflect.Indirect(val) + } + + if !val.IsValid() { + if !b.skipEmpty { + out[fieldName] = nil + } + + continue + } + + if b.skipEmpty && val.IsZero() { + continue + } + + if val.Kind() != reflect.Struct { + out[fieldName] = val.Interface() + + continue + } + + out[fieldName] = b.FromStruct(val.Interface(), tagType) + } + + return out +} + +func (*Builder) ToMapStringAny(t map[any]any) map[string]map[any]any { + out := make(map[string]map[any]any) + + for k, v := range t { + val, ok := v.(map[any]any) + if !ok { + continue + } + + out[fmt.Sprintf("%v", k)] = val + } + + return out +} diff --git a/internal/x/map/build_test.go b/internal/x/map/build_test.go new file mode 100644 index 000000000..da256ec17 --- /dev/null +++ b/internal/x/map/build_test.go @@ -0,0 +1,157 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mapx_test + +import ( + "reflect" + "testing" + + mapx "github.com/sighupio/furyctl/internal/x/map" +) + +func TestFromStruct(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + sIn any + tag string + skipEmpty bool + want map[any]any + }{ + { + "input is nil", + nil, + "", + false, + nil, + }, + { + "input is not a struct", + "not a struct", + "", + false, + nil, + }, + { + "input is a struct with no tags", + struct { + A string + }{ + A: "a", + }, + "json", + false, + map[any]any{"A": "a"}, + }, + { + "input is a struct with correct tags", + struct { + A string `json:"a"` + }{ + A: "a", + }, + "json", + false, + map[any]any{"a": "a"}, + }, + { + "input is a struct with default values", + struct { + A string `json:"a"` + B int `json:"b"` + C struct { + D string `json:"d"` + } `json:"c"` + E *struct { + F string `json:"f"` + } `json:"e"` + }{ + A: "a", + B: 0, + C: struct { + D string `json:"d"` + }{ + D: "", + }, + E: nil, + }, + "json", + true, + map[any]any{ + "a": "a", + }, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + builder := mapx.NewBuilder(tc.skipEmpty) + + got := builder.FromStruct(tc.sIn, tc.tag) + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("expected %v, got %v", tc.want, got) + } + }) + } +} + +func TestToMapStringAny(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + sIn map[any]any + want map[string]map[any]any + }{ + { + "input is nil", + nil, + map[string]map[any]any{}, + }, + { + "input is not a map[any]map[any]any", + map[any]any{ + "key": "value", + }, + map[string]map[any]any{}, + }, + { + "input is a map[any]map[any]any", + map[any]any{ + "key": map[any]any{ + "key": "value", + }, + "notMap": "notMap", + }, + map[string]map[any]any{ + "key": { + "key": "value", + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + builder := mapx.NewBuilder(false) + + got := builder.ToMapStringAny(tc.sIn) + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("expected %v, got %v", tc.want, got) + } + }) + } +} diff --git a/internal/netx/client.go b/internal/x/net/client.go similarity index 100% rename from internal/netx/client.go rename to internal/x/net/client.go diff --git a/internal/netx/hashicorp.go b/internal/x/net/hashicorp.go similarity index 66% rename from internal/netx/hashicorp.go rename to internal/x/net/hashicorp.go index fb5e421b7..154312c2c 100644 --- a/internal/netx/hashicorp.go +++ b/internal/x/net/hashicorp.go @@ -13,22 +13,22 @@ import ( "github.com/sirupsen/logrus" ) -var ( - downloadProtocols = []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"} - - errDownloadOptionsExausted = errors.New("downloading options exausted") -) +var errDownloadOptionsExausted = errors.New("downloading options exausted") func NewGoGetterClient() *GoGetterClient { - return &GoGetterClient{} + return &GoGetterClient{ + protocols: []string{"", "git::", "file::", "http::", "s3::", "gcs::", "mercurial::"}, + } } -type GoGetterClient struct{} +type GoGetterClient struct { + protocols []string +} func (g *GoGetterClient) Download(src, dst string) error { protocols := []string{""} - if !UrlHasForcedProtocol(src) { - protocols = downloadProtocols + if !g.URLHasForcedProtocol(src) { + protocols = g.protocols } for _, protocol := range protocols { @@ -53,9 +53,9 @@ func (g *GoGetterClient) Download(src, dst string) error { return errDownloadOptionsExausted } -// UrlHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. -func UrlHasForcedProtocol(url string) bool { - for _, dp := range downloadProtocols { +// URLHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. +func (g *GoGetterClient) URLHasForcedProtocol(url string) bool { + for _, dp := range g.protocols { if dp != "" && strings.HasPrefix(url, dp) { return true } diff --git a/internal/netx/hashicorp_test.go b/internal/x/net/hashicorp_test.go similarity index 87% rename from internal/netx/hashicorp_test.go rename to internal/x/net/hashicorp_test.go index a7bda1a00..916d7fd76 100644 --- a/internal/netx/hashicorp_test.go +++ b/internal/x/net/hashicorp_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package netx_test import ( @@ -9,10 +11,12 @@ import ( "path/filepath" "testing" - "github.com/sighupio/furyctl/internal/netx" + netx "github.com/sighupio/furyctl/internal/x/net" ) func Test_GoGetterClient_Download(t *testing.T) { + t.Parallel() + tmpDir, err := os.MkdirTemp("", "furyctl-clientget-test-") if err != nil { t.Fatalf("error creating temp dir: %v", err) @@ -32,6 +36,7 @@ func Test_GoGetterClient_Download(t *testing.T) { defer func() { src.Close() + _ = os.RemoveAll(tmpDir) }() @@ -47,6 +52,8 @@ func Test_GoGetterClient_Download(t *testing.T) { } func TestUrlHasForcedProtocol(t *testing.T) { + t.Parallel() + tests := []struct { name string url string @@ -63,9 +70,14 @@ func TestUrlHasForcedProtocol(t *testing.T) { want: false, }, } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { - if got := netx.UrlHasForcedProtocol(tt.url); got != tt.want { + t.Parallel() + + if got := netx.NewGoGetterClient().URLHasForcedProtocol(tt.url); got != tt.want { t.Errorf("urlHasForcedProtocol() = %v, want %v", got, tt.want) } }) diff --git a/internal/osx/path.go b/internal/x/os/path.go similarity index 85% rename from internal/osx/path.go rename to internal/x/os/path.go index 984df6ef1..1fded5134 100644 --- a/internal/osx/path.go +++ b/internal/x/os/path.go @@ -6,13 +6,14 @@ package osx import ( "errors" + "fmt" "os" ) func CleanupTempDir(dir string) error { if err := os.RemoveAll(dir); err != nil { if !errors.Is(err, os.ErrNotExist) { - return err + return fmt.Errorf("error removing dir: %w", err) } } diff --git a/internal/x/os/path_test.go b/internal/x/os/path_test.go new file mode 100644 index 000000000..e37d7a856 --- /dev/null +++ b/internal/x/os/path_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package osx_test + +import ( + "errors" + "os" + "path/filepath" + "testing" + + osx "github.com/sighupio/furyctl/internal/x/os" +) + +func TestCleanupTempDir(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (string, error) + wantErr bool + }{ + { + desc: "directory does not exist", + setup: func() (string, error) { + dir, err := os.MkdirTemp("", "furyctl") + if err != nil { + return "", err + } + + return filepath.Join(dir, "non-existing-directory"), nil + }, + }, + { + desc: "directory exists", + setup: func() (string, error) { + return os.MkdirTemp("", "furyctl") + }, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + dir, err := tC.setup() + if err != nil { + t.Fatalf("error setting up test: %v", err) + } + + err = osx.CleanupTempDir(dir) + + if tC.wantErr && err == nil { + t.Fatalf("expected error, got none") + } + + if !tC.wantErr && err != nil { + t.Fatalf("expected no errors, got = %v", err) + } + + if _, err := os.Stat(dir); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("expected directory to be removed, got = %v", err) + } + }) + } +} diff --git a/internal/x/slices/uniq.go b/internal/x/slices/uniq.go new file mode 100644 index 000000000..ff8e48f9f --- /dev/null +++ b/internal/x/slices/uniq.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +func Uniq[T comparable](s []T) []T { + unique := make(map[T]bool, len(s)) + us := make([]T, 0) + + for _, v := range s { + if !unique[v] { + us = append(us, v) + unique[v] = true + } + } + + return us +} diff --git a/internal/x/slices/uniq_test.go b/internal/x/slices/uniq_test.go new file mode 100644 index 000000000..eb83c33a8 --- /dev/null +++ b/internal/x/slices/uniq_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/x/slices" +) + +func TestUniq(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in []string + want []string + }{ + { + name: "empty", + in: []string{}, + want: []string{}, + }, + { + name: "one element", + in: []string{"a"}, + want: []string{"a"}, + }, + { + name: "two elements", + in: []string{"a", "b"}, + want: []string{"a", "b"}, + }, + { + name: "two elements, one duplicated", + in: []string{"a", "a"}, + want: []string{"a"}, + }, + { + name: "three elements, one duplicated", + in: []string{"a", "b", "a"}, + want: []string{"a", "b"}, + }, + { + name: "three elements, two duplicated", + in: []string{"a", "b", "b"}, + want: []string{"a", "b"}, + }, + { + name: "three elements, all duplicated", + in: []string{"a", "a", "a"}, + want: []string{"a"}, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := slices.Uniq(tt.in) + + if len(got) != len(tt.want) { + t.Errorf("got %d elements, want %d", len(got), len(tt.want)) + } + + for i := range got { + if got[i] != tt.want[i] { + t.Errorf("got %s, want %s", got[i], tt.want[i]) + } + } + }) + } +} diff --git a/internal/x/yaml/yaml.go b/internal/x/yaml/yaml.go new file mode 100644 index 000000000..c2905ab71 --- /dev/null +++ b/internal/x/yaml/yaml.go @@ -0,0 +1,61 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package yamlx + +import ( + "fmt" + "os" + + v2 "gopkg.in/yaml.v2" + v3 "gopkg.in/yaml.v3" +) + +func FromFileV2[T any](path string) (T, error) { + var data T + + res, err := os.ReadFile(path) + if err != nil { + return data, fmt.Errorf("error while reading file from %s :%w", path, err) + } + + if err := v2.Unmarshal(res, &data); err != nil { + return data, fmt.Errorf("error while unmarshalling file from %s :%w", path, err) + } + + return data, nil +} + +func MarshalV2(in any) ([]byte, error) { + out, err := v2.Marshal(in) + if err != nil { + return nil, fmt.Errorf("error while marshalling yaml: %w", err) + } + + return out, nil +} + +func FromFileV3[T any](file string) (T, error) { + var data T + + res, err := os.ReadFile(file) + if err != nil { + return data, fmt.Errorf("error while reading file from %s :%w", file, err) + } + + if err := v3.Unmarshal(res, &data); err != nil { + return data, fmt.Errorf("error while unmarshalling file from %s :%w", file, err) + } + + return data, nil +} + +func MarshalV3(in any) ([]byte, error) { + out, err := v3.Marshal(in) + if err != nil { + return nil, fmt.Errorf("error while marshalling yaml: %w", err) + } + + return out, nil +} diff --git a/internal/x/yaml/yaml_test.go b/internal/x/yaml/yaml_test.go new file mode 100644 index 000000000..053a4b3b2 --- /dev/null +++ b/internal/x/yaml/yaml_test.go @@ -0,0 +1,80 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package yamlx_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +type TestYaml struct { + Test string `yaml:"test"` +} + +func TestFromFileV2(t *testing.T) { + test := TestYaml{ + "test", + } + + path, err := os.MkdirTemp("", "test") + + assert.NoError(t, err) + + file, err := os.Create(path + "/test.yaml") + + assert.NoError(t, err) + + testBytes, err := yamlx.MarshalV2(test) + + assert.NoError(t, err) + + _, err = file.Write(testBytes) + + assert.NoError(t, err) + + defer os.RemoveAll(path) + + testRes, err := yamlx.FromFileV2[TestYaml](path + "/test.yaml") + + assert.NoError(t, err) + + assert.Equal(t, test, testRes) +} + +func TestFromFileV3(t *testing.T) { + test := TestYaml{ + "test", + } + + path, err := os.MkdirTemp("", "test") + + assert.NoError(t, err) + + file, err := os.Create(path + "/test.yaml") + + assert.NoError(t, err) + + testBytes, err := yamlx.MarshalV3(test) + + assert.NoError(t, err) + + _, err = file.Write(testBytes) + + assert.NoError(t, err) + + defer os.RemoveAll(path) + + testRes, err := yamlx.FromFileV3[TestYaml](path + "/test.yaml") + + assert.NoError(t, err) + + assert.Equal(t, test, testRes) +} diff --git a/internal/yaml/yaml.go b/internal/yaml/yaml.go deleted file mode 100644 index 6c5fa2faf..000000000 --- a/internal/yaml/yaml.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package yaml - -import ( - "os" - - v2 "gopkg.in/yaml.v2" - v3 "gopkg.in/yaml.v3" -) - -func FromFileV2[T any](path string) (T, error) { - var data T - - res, err := os.ReadFile(path) - if err != nil { - return data, err - } - - err = v2.Unmarshal(res, &data) - - return data, err -} - -func MarshalV2(in any) ([]byte, error) { - return v2.Marshal(in) -} - -func FromFileV3[T any](file string) (T, error) { - var data T - - res, err := os.ReadFile(file) - if err != nil { - return data, err - } - - if err := v3.Unmarshal(res, &data); err != nil { - return data, err - } - - return data, err -} - -func MarshalV3(in any) ([]byte, error) { - return v3.Marshal(in) -} diff --git a/internal/yaml/yaml_test.go b/internal/yaml/yaml_test.go deleted file mode 100644 index 67cef9090..000000000 --- a/internal/yaml/yaml_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package yaml_test - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - yaml2 "gopkg.in/yaml.v2" - - yaml "github.com/sighupio/furyctl/internal/yaml" -) - -type TestYaml struct { - Test string `yaml:"test"` -} - -func TestFromFileV2(t *testing.T) { - test := TestYaml{ - "test", - } - - path, err := os.MkdirTemp("", "test") - - assert.NoError(t, err) - - file, err := os.Create(path + "/test.yaml") - - assert.NoError(t, err) - - testBytes, err := yaml2.Marshal(test) - - assert.NoError(t, err) - - _, err = file.Write(testBytes) - - assert.NoError(t, err) - - defer os.RemoveAll(path) - - testRes, err := yaml.FromFileV2[TestYaml](path + "/test.yaml") - - assert.NoError(t, err) - - assert.Equal(t, test, testRes) -} diff --git a/main.go b/main.go index 9eb6241c1..d1841550f 100644 --- a/main.go +++ b/main.go @@ -15,9 +15,13 @@ package main import ( + "os" + "runtime" + "github.com/sirupsen/logrus" "github.com/sighupio/furyctl/cmd" + "github.com/sighupio/furyctl/internal/analytics" ) var ( @@ -29,6 +33,12 @@ var ( ) func main() { + os.Exit(exec()) +} + +func exec() int { + var logFile *os.File + versions := map[string]string{ "version": version, "gitCommit": gitCommit, @@ -37,7 +47,39 @@ func main() { "osArch": osArch, } - if err := cmd.NewRootCommand(versions).Execute(); err != nil { - logrus.Fatal(err) + defer logFile.Close() + + log := &logrus.Logger{ + Out: os.Stdout, + Formatter: &logrus.TextFormatter{ + ForceColors: true, + DisableTimestamp: true, + }, + Level: logrus.DebugLevel, + } + + h, err := os.Hostname() + if err != nil { + log.Debug(err) + + h = "unknown" + } + + t := os.Getenv("FURYCTL_MIXPANEL_TOKEN") + if t == "" { + log.Debug("FURYCTL_MIXPANEL_TOKEN is not set") + } + + // Create the analytics tracker. + a := analytics.NewTracker(t, versions[version], osArch, runtime.GOOS, "SIGHUP", h) + + defer a.Flush() + + if _, err := cmd.NewRootCommand(versions, logFile, a).ExecuteC(); err != nil { + log.Error(err) + + return 1 } + + return 0 } diff --git a/test/data/e2e/create/cluster/bin_mock/aws b/test/data/e2e/create/cluster/bin_mock/aws new file mode 100755 index 000000000..45ec3ca6d --- /dev/null +++ b/test/data/e2e/create/cluster/bin_mock/aws @@ -0,0 +1,5 @@ +#!/bin/sh + +cat < +Compile time defines: enable_async_push=no enable_comp_stub=no enable_crypto_ofb_cfb=yes enable_debug=no enable_def_auth=yes enable_dependency_tracking=no enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown enable_fast_install=needless enable_fragment=yes enable_iproute2=no enable_libtool_lock=yes enable_lz4=yes enable_lzo=yes enable_management=yes enable_multihome=yes enable_pam_dlopen=no enable_pedantic=no enable_pf=yes enable_pkcs11=yes enable_plugin_auth_pam=yes enable_plugin_down_root=yes enable_plugins=yes enable_port_share=yes enable_selinux=no enable_shared=yes enable_shared_with_static_runtimes=no enable_silent_rules=no enable_small=no enable_static=yes enable_strict=no enable_strict_options=no enable_systemd=no enable_werror=no enable_win32_dll=yes enable_x509_alt_username=no with_aix_soname=aix with_crypto_library=openssl with_gnu_ld=no with_mem_check=no with_openssl_engine=auto with_sysroot=no +EOF diff --git a/test/data/e2e/create/cluster/bin_mock/terraform b/test/data/e2e/create/cluster/bin_mock/terraform new file mode 100755 index 000000000..00882a6dc --- /dev/null +++ b/test/data/e2e/create/cluster/bin_mock/terraform @@ -0,0 +1,6 @@ +#!/bin/sh + +cat <[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{17}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", + "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + } + }, + "Types.FuryModuleOverridesIngress": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/test/data/v1.23.3/furyctl.yaml b/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml similarity index 98% rename from test/data/v1.23.3/furyctl.yaml rename to test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml index 8c9864001..15b304920 100644 --- a/test/data/v1.23.3/furyctl.yaml +++ b/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml @@ -7,7 +7,7 @@ kind: EKSCluster metadata: name: awesome-cluster-staging spec: - distributionVersion: "v1.23.3" + distributionVersion: v1.24.1 toolsConfiguration: terraform: state: @@ -56,7 +56,7 @@ spec: apiServerEndpointAccess: type: private allowedCidrs: - - 10.1.0.0/16 + - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" nodePools: - name: worker @@ -137,11 +137,9 @@ spec: type: http01 dns: public: - enabled: true name: "fury-demo.sighup.io" create: false private: - enabled: true name: "internal.fury-demo.sighup.io" vpcId: "vpc123123123123" logging: @@ -166,7 +164,7 @@ spec: limits: cpu: "" memory: "" - storage_request: "" + storageSize: "150Gi" monitoring: overrides: nodeSelector: {} diff --git a/test/integration/template-engine/data/complex/data/expected-kustomization.yaml b/test/data/e2e/dump/template/complex-dry-run/data/expected-kustomization.yaml similarity index 85% rename from test/integration/template-engine/data/complex/data/expected-kustomization.yaml rename to test/data/e2e/dump/template/complex-dry-run/data/expected-kustomization.yaml index 3e239a498..d1447784a 100644 --- a/test/integration/template-engine/data/complex/data/expected-kustomization.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/data/expected-kustomization.yaml @@ -10,9 +10,7 @@ resources: - ../../vendor/katalog/ingress/cert-manager - ../../vendor/katalog/ingress/nginx - ../../vendor/katalog/ingress/forecastle - - resources/cert-manager-clusterissuer.yml patchesStrategicMerge: - - patches/cert-manager.yml - patches/infra-nodes.yml - patches/ingress-nginx.yml diff --git a/test/integration/template-engine/data/complex/distribution.yaml b/test/data/e2e/dump/template/complex-dry-run/furyctl-defaults.yaml similarity index 99% rename from test/integration/template-engine/data/complex/distribution.yaml rename to test/data/e2e/dump/template/complex-dry-run/furyctl-defaults.yaml index 905a6867d..997ccf89b 100644 --- a/test/integration/template-engine/data/complex/distribution.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/furyctl-defaults.yaml @@ -88,7 +88,7 @@ data: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: "150Gi" # override ingresses parameters # monitoring module configuration monitoring: @@ -147,7 +147,7 @@ data: eks: iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role region: eu-west-1 - bucket: velero-bucket + bucketName: velero-bucket # auth module configuration auth: overrides: diff --git a/test/integration/template-engine/data/complex-dry-run/furyctl.yaml b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml similarity index 94% rename from test/integration/template-engine/data/complex-dry-run/furyctl.yaml rename to test/data/e2e/dump/template/complex-dry-run/furyctl.yaml index c200caab5..cc2e652b9 100644 --- a/test/integration/template-engine/data/complex-dry-run/furyctl.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml @@ -8,15 +8,15 @@ metadata: # the name will be used as a prefix/suffix for all the managed resources name: awesome-cluster-staging spec: + region: eu-west-1 # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: "v1.24.7" + distributionVersion: v1.24.7 # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: terraform: state: - backend: s3 - config: - bucket: awesome-bucket-created-outside-furyctl + s3: + bucketName: awesome-bucket-created-outside-furyctl # changed from key, because each terraform project state will be placed in the directory defined by the prefix keyPrefix: furyctl/ region: eu-west-1 @@ -59,22 +59,14 @@ spec: # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal publicKeys: - "ssh-ed56778 XYX" - - {file://relative/path/to/ssh.pub} +# - {file://relative/path/to/ssh.pub} # Github users to get ssh keys from githubUsersName: - lnovara # the CIDRs that are allowed for the ssh connection allowedFromCidrs: - 0.0.0.0/0 - cluster: - # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase - # and will be automatically gathered from the output of the bootstrap phase - vpcId: vpc-0f92da9b4a2089963 - # optional, see the above comment - subnetIds: - - subnet-0ab84702287e38ccb - - subnet-0ae4e9199d9192226 - - subnet-01787e8da51e4f070 + kubernetes: # cidr allowed to talk with the apiServer apiServerAllowedCidrs: - 10.1.0.0/16 @@ -137,7 +129,7 @@ spec: groups: - sighup-support:masters rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" - # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + # distribution configuration, a subset of the `furyctl-defaults.yaml` file and some additional configurations for prerequisites # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules distribution: common: @@ -225,7 +217,7 @@ spec: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: "150Gi" # override ingresses parameters # monitoring module configuration monitoring: @@ -278,6 +270,11 @@ spec: overrides: nodeSelector: {} tolerations: {} + velero: # required. + eks: # required. + bucketName: example-velero # required. + iamRoleArn: arn:aws:iam::123456789012:role/example-velero # private. + region: eu-west-1 # required. # auth module configuration auth: overrides: diff --git a/test/integration/template-engine/data/complex-dry-run/source/config/example.yaml b/test/data/e2e/dump/template/complex-dry-run/templates/distribution/config/example.yaml similarity index 100% rename from test/integration/template-engine/data/complex-dry-run/source/config/example.yaml rename to test/data/e2e/dump/template/complex-dry-run/templates/distribution/config/example.yaml diff --git a/test/data/e2e/dump/template/complex-dry-run/templates/distribution/kustomization.yaml.tpl b/test/data/e2e/dump/template/complex-dry-run/templates/distribution/kustomization.yaml.tpl new file mode 100644 index 000000000..6393adb22 --- /dev/null +++ b/test/data/e2e/dump/template/complex-dry-run/templates/distribution/kustomization.yaml.tpl @@ -0,0 +1,28 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +{{ if .spec.distribution.modules.ingress }} +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/cert-manager +{{- if eq .spec.distribution.modules.ingress.nginx.type "dual" }} + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/dual-nginx +{{- end }} +{{- if eq .spec.distribution.modules.ingress.nginx.type "single" }} + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/nginx +{{- end }} + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/forecastle +{{- if .spec.distribution.modules.ingress.certManager.clusterIssuer.notExistingProperty }} + - resources/cert-manager-clusterissuer.yml +{{- end }} + +patchesStrategicMerge: +{{- if .spec.distribution.modules.ingress.certManager.clusterIssuer.notExistingProperty }} + - patches/cert-manager.yml +{{- end }} + - patches/infra-nodes.yml + - patches/ingress-nginx.yml +{{- end }} diff --git a/test/integration/template-engine/data/complex-dry-run/data/expected-kustomization.yaml b/test/data/e2e/dump/template/complex/data/expected-kustomization.yaml similarity index 100% rename from test/integration/template-engine/data/complex-dry-run/data/expected-kustomization.yaml rename to test/data/e2e/dump/template/complex/data/expected-kustomization.yaml diff --git a/test/integration/template-engine/data/complex-dry-run/distribution.yaml b/test/data/e2e/dump/template/complex/furyctl-defaults.yaml similarity index 99% rename from test/integration/template-engine/data/complex-dry-run/distribution.yaml rename to test/data/e2e/dump/template/complex/furyctl-defaults.yaml index 905a6867d..997ccf89b 100644 --- a/test/integration/template-engine/data/complex-dry-run/distribution.yaml +++ b/test/data/e2e/dump/template/complex/furyctl-defaults.yaml @@ -88,7 +88,7 @@ data: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: "150Gi" # override ingresses parameters # monitoring module configuration monitoring: @@ -147,7 +147,7 @@ data: eks: iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role region: eu-west-1 - bucket: velero-bucket + bucketName: velero-bucket # auth module configuration auth: overrides: diff --git a/test/integration/template-engine/data/complex/furyctl.yaml b/test/data/e2e/dump/template/complex/furyctl.yaml similarity index 94% rename from test/integration/template-engine/data/complex/furyctl.yaml rename to test/data/e2e/dump/template/complex/furyctl.yaml index c200caab5..b7b2f52da 100644 --- a/test/integration/template-engine/data/complex/furyctl.yaml +++ b/test/data/e2e/dump/template/complex/furyctl.yaml @@ -8,15 +8,15 @@ metadata: # the name will be used as a prefix/suffix for all the managed resources name: awesome-cluster-staging spec: + region: eu-west-1 # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: "v1.24.7" + distributionVersion: v1.24.7 # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: terraform: state: - backend: s3 - config: - bucket: awesome-bucket-created-outside-furyctl + s3: + bucketName: awesome-bucket-created-outside-furyctl # changed from key, because each terraform project state will be placed in the directory defined by the prefix keyPrefix: furyctl/ region: eu-west-1 @@ -59,22 +59,14 @@ spec: # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal publicKeys: - "ssh-ed56778 XYX" - - {file://relative/path/to/ssh.pub} +# - {file://relative/path/to/ssh.pub} # Github users to get ssh keys from githubUsersName: - lnovara # the CIDRs that are allowed for the ssh connection allowedFromCidrs: - 0.0.0.0/0 - cluster: - # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase - # and will be automatically gathered from the output of the bootstrap phase - vpcId: vpc-0f92da9b4a2089963 - # optional, see the above comment - subnetIds: - - subnet-0ab84702287e38ccb - - subnet-0ae4e9199d9192226 - - subnet-01787e8da51e4f070 + kubernetes: # cidr allowed to talk with the apiServer apiServerAllowedCidrs: - 10.1.0.0/16 @@ -137,7 +129,7 @@ spec: groups: - sighup-support:masters rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" - # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + # distribution configuration, a subset of the `furyctl-defaults.yaml` file and some additional configurations for prerequisites # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules distribution: common: @@ -225,7 +217,7 @@ spec: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: "150Gi" # override ingresses parameters # monitoring module configuration monitoring: diff --git a/test/integration/template-engine/data/complex/source/config/example.yaml b/test/data/e2e/dump/template/complex/templates/distribution/config/example.yaml similarity index 100% rename from test/integration/template-engine/data/complex/source/config/example.yaml rename to test/data/e2e/dump/template/complex/templates/distribution/config/example.yaml diff --git a/test/data/e2e/dump/template/complex/templates/distribution/kustomization.yaml.tpl b/test/data/e2e/dump/template/complex/templates/distribution/kustomization.yaml.tpl new file mode 100644 index 000000000..1b8feeca7 --- /dev/null +++ b/test/data/e2e/dump/template/complex/templates/distribution/kustomization.yaml.tpl @@ -0,0 +1,28 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +{{ if .spec.distribution.modules.ingress }} +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/cert-manager +{{- if eq .spec.distribution.modules.ingress.nginx.type "dual" }} + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/dual-nginx +{{- end }} +{{- if eq .spec.distribution.modules.ingress.nginx.type "single" }} + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/nginx +{{- end }} + - {{ .spec.distribution.common.relativeVendorPath }}/katalog/ingress/forecastle +{{- if .spec.distribution.modules.ingress.certManager.clusterIssuer.name }} + - resources/cert-manager-clusterissuer.yml +{{- end }} + +patchesStrategicMerge: +{{- if .spec.distribution.modules.ingress.certManager.clusterIssuer.name }} + - patches/cert-manager.yml +{{- end }} + - patches/infra-nodes.yml + - patches/ingress-nginx.yml +{{- end }} diff --git a/test/integration/template-engine/data/distribution-yaml-no-data-property/distribution.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl-defaults.yaml similarity index 100% rename from test/integration/template-engine/data/distribution-yaml-no-data-property/distribution.yaml rename to test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl-defaults.yaml diff --git a/test/integration/template-engine/data/distribution-yaml-no-data-property/furyctl.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl.yaml similarity index 100% rename from test/integration/template-engine/data/distribution-yaml-no-data-property/furyctl.yaml rename to test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl.yaml diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution/file.txt.tpl b/test/data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution/file.txt.tpl new file mode 100644 index 000000000..93e9e7cf8 --- /dev/null +++ b/test/data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution/file.txt.tpl @@ -0,0 +1 @@ +{{.spec.distribution.test.hello}} diff --git a/test/integration/template-engine/data/distribution-yaml-no-data-property/source/keepfile.txt b/test/data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution/keepfile.txt similarity index 100% rename from test/integration/template-engine/data/distribution-yaml-no-data-property/source/keepfile.txt rename to test/data/e2e/dump/template/distribution-yaml-no-data-property/templates/distribution/keepfile.txt diff --git a/test/integration/template-engine/data/empty/source/file.txt.tpl b/test/data/e2e/dump/template/empty/templates/distribution/file.txt.tpl similarity index 100% rename from test/integration/template-engine/data/empty/source/file.txt.tpl rename to test/data/e2e/dump/template/empty/templates/distribution/file.txt.tpl diff --git a/test/integration/template-engine/data/no-distribution-yaml/.gitkeep b/test/data/e2e/dump/template/no-distribution-yaml/.gitkeep similarity index 100% rename from test/integration/template-engine/data/no-distribution-yaml/.gitkeep rename to test/data/e2e/dump/template/no-distribution-yaml/.gitkeep diff --git a/test/integration/template-engine/data/no-furyctl-yaml/distribution.yaml b/test/data/e2e/dump/template/no-furyctl-yaml/furyctl-defaults.yaml similarity index 100% rename from test/integration/template-engine/data/no-furyctl-yaml/distribution.yaml rename to test/data/e2e/dump/template/no-furyctl-yaml/furyctl-defaults.yaml diff --git a/test/integration/template-engine/data/simple-dry-run/distribution.yaml b/test/data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml similarity index 100% rename from test/integration/template-engine/data/simple-dry-run/distribution.yaml rename to test/data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml diff --git a/test/integration/template-engine/data/simple-dry-run/furyctl.yaml b/test/data/e2e/dump/template/simple-dry-run/furyctl.yaml similarity index 100% rename from test/integration/template-engine/data/simple-dry-run/furyctl.yaml rename to test/data/e2e/dump/template/simple-dry-run/furyctl.yaml diff --git a/test/data/e2e/dump/template/simple-dry-run/templates/distribution/file.txt.tpl b/test/data/e2e/dump/template/simple-dry-run/templates/distribution/file.txt.tpl new file mode 100644 index 000000000..93e9e7cf8 --- /dev/null +++ b/test/data/e2e/dump/template/simple-dry-run/templates/distribution/file.txt.tpl @@ -0,0 +1 @@ +{{.spec.distribution.test.hello}} diff --git a/test/integration/template-engine/data/simple-dry-run/source/keepfile.txt b/test/data/e2e/dump/template/simple-dry-run/templates/distribution/keepfile.txt similarity index 100% rename from test/integration/template-engine/data/simple-dry-run/source/keepfile.txt rename to test/data/e2e/dump/template/simple-dry-run/templates/distribution/keepfile.txt diff --git a/test/integration/template-engine/data/simple/distribution.yaml b/test/data/e2e/dump/template/simple/furyctl-defaults.yaml similarity index 100% rename from test/integration/template-engine/data/simple/distribution.yaml rename to test/data/e2e/dump/template/simple/furyctl-defaults.yaml diff --git a/test/integration/template-engine/data/simple/furyctl.yaml b/test/data/e2e/dump/template/simple/furyctl.yaml similarity index 100% rename from test/integration/template-engine/data/simple/furyctl.yaml rename to test/data/e2e/dump/template/simple/furyctl.yaml diff --git a/test/data/e2e/dump/template/simple/templates/distribution/file.txt.tpl b/test/data/e2e/dump/template/simple/templates/distribution/file.txt.tpl new file mode 100644 index 000000000..93e9e7cf8 --- /dev/null +++ b/test/data/e2e/dump/template/simple/templates/distribution/file.txt.tpl @@ -0,0 +1 @@ +{{.spec.distribution.test.hello}} diff --git a/test/integration/template-engine/data/simple/source/keepfile.txt b/test/data/e2e/dump/template/simple/templates/distribution/keepfile.txt similarity index 100% rename from test/integration/template-engine/data/simple/source/keepfile.txt rename to test/data/e2e/dump/template/simple/templates/distribution/keepfile.txt diff --git a/test/data/v1.23.3/distro/furyctl-defaults.yaml b/test/data/e2e/validate/config/correct/furyctl-defaults.yaml similarity index 74% rename from test/data/v1.23.3/distro/furyctl-defaults.yaml rename to test/data/e2e/validate/config/correct/furyctl-defaults.yaml index e1454aeaa..707d13c4d 100644 --- a/test/data/v1.23.3/distro/furyctl-defaults.yaml +++ b/test/data/e2e/validate/config/correct/furyctl-defaults.yaml @@ -24,8 +24,8 @@ data: # ingress module configuration ingress: overrides: - nodeSelector: {} - tolerations: [] + nodeSelector: null + tolerations: null # override ingresses parameters ingresses: forecastle: @@ -36,6 +36,22 @@ data: ingressClass: "" baseDomain: example.dev + externalDns: + privateIamRoleArn: arn:aws:iam::123456789012:role/external-dns-private + publicIamRoleArn: arn:aws:iam::123456789012:role/external-dns-public + dns: + public: + name: "" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + # private is used only when ingress.nginx.type is "dual" + private: + # required to be set by the user, ex: internal.fury-demo.sighup.io + name: "" + create: true + # internal field, should be either the VPC ID taken from the kubernetes + # phase or the ID of the created VPC in the Ifra phase + vpcId: "" # common configuration for nginx ingress controller nginx: # can be single or dual @@ -55,20 +71,21 @@ data: # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity clusterIssuer: name: letsencrypt-fury - # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + email: engineering+fury-distribution@sighup.io + # can be dns01 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external type: http01 # if auth type is route53, we need to provide the following configurations route53: - iamRoleArn: arn:aws:iam::123456789012:role/example-cert-manager + iamRoleArn: arn:aws:iam::123456789012:role/example-route53 region: eu-west-1 - hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + hostedZoneId: "" # logging module configuration logging: overrides: - nodeSelector: {} - tolerations: [] + nodeSelector: null + tolerations: null ingresses: - opensearch-dashboards: + opensearchDashboards: disableAuth: false host: "" ingressClass: "" @@ -76,6 +93,10 @@ data: disableAuth: false host: "" ingressClass: "" + minio: + disableAuth: false + host: "" + ingressClass: "" opensearch: # can be single or triple type: single @@ -88,13 +109,13 @@ data: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: 150Gi # override ingresses parameters # monitoring module configuration monitoring: overrides: - nodeSelector: {} - tolerations: [] + nodeSelector: null + tolerations: null # override ingresses parameters ingresses: prometheus: @@ -109,10 +130,6 @@ data: disableAuth: false host: "" ingressClass: "" - goldpinger: - disableAuth: false - host: "" - ingressClass: "" prometheus: resources: requests: @@ -121,11 +138,17 @@ data: limits: cpu: "" memory: "" + retentionTime: 30d + retentionSize: 120GB + storageSize: 150Gi + alertmanager: + deadManSwitchWebhookUrl: "" + slackWebhookUrl: "" # policy module configuration policy: overrides: - nodeSelector: {} - tolerations: [] + nodeSelector: null + tolerations: null # override ingresses parameters ingresses: gpm: @@ -139,19 +162,19 @@ data: # dr module configuration dr: overrides: - nodeSelector: {} - tolerations: [] + nodeSelector: null + tolerations: null # the standard configuration for velero on the dr module velero: # this configuration will be used if common.provider.type is eks eks: iamRoleArn: arn:aws:iam::123456789012:role/example-velero region: eu-west-1 - bucket: example-velero + bucketName: example-velero # auth module configuration auth: overrides: - nodeSelector: {} + nodeSelector: null # override ingresses parameters ingresses: pomerium: @@ -161,7 +184,7 @@ data: dex: host: "" ingressClass: "" - tolerations: [] + tolerations: null provider: # can be none, basicAuth or sso. SSO uses pomerium+dex type: none @@ -169,6 +192,7 @@ data: username: admin password: admin pomerium: + policy: "" secrets: # override environment variables here ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret @@ -181,8 +205,15 @@ data: # see dex documentation for more information connectors: [] aws: + overrides: + nodeSelector: null + tolerations: null + ebsCsiDriver: + iamRoleArn: arn:aws:iam::123456789012:role/ebs-csi-controller-role + loadBalancerController: + iamRoleArn: arn:aws:iam::123456789012:role/example-load-balancer-controller clusterAutoscaler: - nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/example + region: eu-west-1 iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler templates: includes: diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml new file mode 100644 index 000000000..77aacd2f7 --- /dev/null +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -0,0 +1,206 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: furyctl-dev-aws +spec: + distributionVersion: "v1.24.1" + toolsConfiguration: + terraform: + state: + s3: + bucketName: furyctl-test + keyPrefix: furyctl/ + region: eu-west-1 + region: eu-west-1 + tags: + env: "test" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.0.0.0/16 + subnetsCidrs: + private: + - 10.0.182.0/24 + - 10.0.172.0/24 + - 10.0.162.0/24 + public: + - 10.0.20.0/24 + - 10.0.30.0/24 + - 10.0.40.0/24 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePools: + - name: worker + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 1 + max: 3 + subnetIds: [] + instance: + type: t3.micro + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: + - node.kubernetes.io/role=worker:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + - name: worker-eks + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 1 + max: 3 + subnetIds: [] + instance: + type: t3.micro + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: [] + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + awsAuth: + additionalAccounts: [] + users: [] + roles: [] + distribution: + common: + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + modules: + ingress: + baseDomain: internal.fury-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + secret: + cert: "{file://relative/path/to/ssl.crt}" + key: "{file://relative/path/to/ssl.key}" + ca: "{file://relative/path/to/ssl.ca}" + certManager: + clusterIssuer: + name: letsencrypt-fury + email: engineering+fury-distribution@sighup.io + type: http01 + dns: + public: + name: "fury-demo.sighup.io" + create: false + private: + create: true + name: "internal.fury-demo.sighup.io" + vpcId: "vpc123123123123" + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + type: single + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + storageSize: "150Gi" + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + policy: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + gatekeeper: + additionalExcludedNamespaces: [] + dr: + velero: + eks: + bucketName: example-velero + iamRoleArn: arn:aws:iam::123456789012:role/example-velero # private. + region: eu-west-1 + auth: + provider: + type: none + basicAuth: + username: admin + password: "{env://KFD_BASIC_AUTH_PASSWORD}" diff --git a/test/data/v1.23.3/distro/kfd.yaml b/test/data/e2e/validate/config/correct/kfd.yaml similarity index 57% rename from test/data/v1.23.3/distro/kfd.yaml rename to test/data/e2e/validate/config/correct/kfd.yaml index 6983f5ab1..3f88aae50 100644 --- a/test/data/v1.23.3/distro/kfd.yaml +++ b/test/data/e2e/validate/config/correct/kfd.yaml @@ -2,25 +2,34 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.23.3 +version: v1.24.1 modules: auth: v0.0.1 + aws: v2.0.0 dr: v1.9.3 ingress: v1.12.2 logging: v2.0.3 monitoring: v1.14.2 opa: v1.7.0 + networking: v1.10.0 kubernetes: eks: - version: 1.23 - installer: v1.9.1 + version: 1.24 + installer: v1.10.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 kind: EKSCluster tools: - ansible: 2.9.27 - furyagent: 0.3.0 - kubectl: 1.23.10 - kustomize: 3.10.0 - terraform: 0.15.4 + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: 2.8.12 diff --git a/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json new file mode 100644 index 000000000..37842f285 --- /dev/null +++ b/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json @@ -0,0 +1,1569 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["EKSCluster"] + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "$defs": { + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 19 + } + }, + "required": [ + "name" + ] + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "distributionVersion": { + "$ref": "#/$defs/Types.SemVer" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "toolsConfiguration": { + "$ref": "#/$defs/Spec.ToolsConfiguration" + }, + "infrastructure": { + "$ref": "#/$defs/Spec.Infrastructure" + }, + "kubernetes": { + "$ref": "#/$defs/Spec.Kubernetes" + }, + "distribution": { + "$ref": "#/$defs/Spec.Distribution" + } + }, + "required": [ + "distributionVersion", + "region", + "kubernetes", + "distribution", + "toolsConfiguration" + ], + "if": { + "anyOf": [ + { + "properties": { + "infrastructure": { + "type": "null" + } + } + }, + { + "properties": { + "infrastructure": { + "properties": { + "vpc": { + "type": "null" + } + } + } + } + }, + { + "properties": { + "infrastructure": { + "properties": { + "vpc": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "kubernetes": { + "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + } + } + }, + "else": { + "properties": { + "kubernetes": { + "type": "object", + "properties": { + "vpcId": { + "type": "null" + }, + "subnetIds": { + "type": "null" + }, + "apiServerEndpointAccess": { + "type": "null" + } + } + } + } + } + }, + + "Spec.ToolsConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" + } + }, + "required": [ + "terraform" + ] + }, + "Spec.ToolsConfiguration.Terraform": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" + } + }, + "required": [ + "state" + ] + }, + "Spec.ToolsConfiguration.Terraform.State": { + "type": "object", + "additionalProperties": false, + "properties": { + "s3": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" + } + }, + "required": [ + "s3" + ] + }, + "Spec.ToolsConfiguration.Terraform.State.S3": { + "type": "object", + "additionalProperties": false, + "properties": { + "bucketName": { + "type": "string" + }, + "keyPrefix": { + "type": "string", + "maxLength": 37 + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "bucketName", + "keyPrefix", + "region" + ] + }, + + "Spec.Infrastructure": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpc": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc" + } + } + }, + "Spec.Infrastructure.Vpc": { + "type": "object", + "additionalProperties": false, + "properties": { + "network": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" + } + }, + "required": [ + "network" + ] + }, + "Spec.Infrastructure.Vpc.Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "subnetsCidrs": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" + } + }, + "required": [ + "cidr", + "subnetsCidrs" + ] + }, + "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { + "type": "object", + "additionalProperties": false, + "properties": { + "private": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "public": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "private", + "public" + ] + }, + "Spec.Infrastructure.Vpc.Vpn": { + "type": "object", + "additionalProperties": false, + "properties": { + "instances": { + "type": "integer" + }, + "port": { + "$ref": "#/$defs/Types.TcpPort" + }, + "instanceType": { + "type": "string" + }, + "diskSize": { + "type": "integer" + }, + "operatorName": { + "type": "string" + }, + "dhParamsBits": { + "type": "integer" + }, + "vpnClientsSubnetCidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "ssh": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + } + }, + "required": [ + "ssh", + "vpnClientsSubnetCidr" + ] + }, + "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "type": "object", + "additionalProperties": false, + "properties": { + "publicKeys": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + } + }, + "githubUsersName": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedFromCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedFromCidrs", + "githubUsersName", + "publicKeys" + ] + }, + + "Spec.Kubernetes": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "apiServerEndpointAccess": { + "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + }, + "nodeAllowedSshPublicKey": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + }, + "nodePools": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool" + } + }, + "awsAuth": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" + } + }, + "required": [ + "nodeAllowedSshPublicKey", + "nodePools" + ] + }, + "Spec.Kubernetes.APIServerEndpointAccess": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "public", + "private", + "public_and_private" + ] + }, + "allowedCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 1 + } + }, + "required": [ + "allowedCidrs", + "type" + ] + }, + "Spec.Kubernetes.NodePool": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "ami": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" + }, + "size": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" + }, + "instance": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" + }, + "attachedTargetGroups": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "taints": { + "$ref": "#/$defs/Types.KubeTaints" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "additionalFirewallRules": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" + } + } + }, + "required": [ + "ami", + "instance", + "name", + "size" + ] + }, + "Spec.Kubernetes.NodePool.Ami": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "owner" + ] + }, + "Spec.Kubernetes.NodePool.Instance": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "spot": { + "type": "boolean" + }, + "volumeSize": { + "type": "integer" + } + }, + "required": [ + "type" + ] + }, + "Spec.Kubernetes.NodePool.Size": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer", + "minimum": 0 + }, + "max": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "max", + "min" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/$defs/Types.TcpPort" + }, + "to": { + "$ref": "#/$defs/Types.TcpPort" + } + }, + "required": [ + "from", + "to" + ] + }, + "Spec.Kubernetes.AwsAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" + } + } + } + }, + "Spec.Kubernetes.AwsAuth.Role": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "rolearn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "rolearn", + "username" + ] + }, + "Spec.Kubernetes.AwsAuth.User": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "userarn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "userarn", + "username" + ] + }, + + "Spec.Distribution": { + "type": "object", + "additionalProperties": false, + "properties": { + "common": { + "$ref": "#/$defs/Spec.Distribution.Common" + }, + "modules": { + "$ref": "#/$defs/Spec.Distribution.Modules" + } + }, + "required": [ + "modules" + ], + "if": { + "allOf": [ + { + "required": ["common"] + }, + { + "properties": { + "common": { + "required": ["provider"] + } + } + }, + { + "properties": { + "common": { + "properties": { + "provider": { + "required": ["type"] + } + } + } + } + }, + { + "properties": { + "common": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "eks" + } + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "modules": { + "required": ["aws"] + } + } + }, + "else": { + "properties": { + "modules": { + "properties": { + "aws": { + "type": "null" + } + } + } + } + } + }, + "Spec.Distribution.Common": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Common.Provider" + }, + "relativeVendorPath": { + "type": "string" + } + } + }, + "Spec.Distribution.Common.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth" + }, + "aws": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws" + }, + "dr": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr" + }, + "ingress": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" + }, + "logging": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging" + }, + "monitoring": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" + }, + "policy": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy" + } + }, + "required": [ + "dr", + "ingress", + "logging" + ] + }, + "Spec.Distribution.Modules.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "baseDomain": { + "type": "string" + }, + "nginx": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" + }, + "certManager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager" + }, + "dns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" + }, + "externalDns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + } + }, + "required": [ + "baseDomain", + "dns", + "nginx" + ] + }, + "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecastle": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + }, + "Spec.Distribution.Modules.Ingress.Nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "dual"] + }, + "tls": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "enum": ["certManager", "secret", "none"] + }, + "secret": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" + } + }, + "required": [ + "provider" + ], + "if": { + "properties": { + "provider": { + "const": "secret" + } + } + }, + "then": { + "required": ["secret"] + } + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ca": { + "type": "string" + } + }, + "required": [ + "ca", + "cert", + "key" + ] + }, + "Spec.Distribution.Modules.Ingress.CertManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "clusterIssuer": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + } + }, + "required": [ + "clusterIssuer" + ] + }, + "Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "type": { + "type": "string", + "enum": ["dns01", "http01"] + }, + "route53": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" + } + }, + "required": [ + "name", + "type", + "email" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "hostedZoneId": { + "type": "string" + } + }, + "required": [ + "hostedZoneId", + "iamRoleArn", + "region" + ] + }, + "Spec.Distribution.Modules.Ingress.ExternalDNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "privateIamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "publicIamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "privateIamRoleArn", + "publicIamRoleArn" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "public": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" + }, + "private": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + } + }, + "required": [ + "public", + "private" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Public": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + } + }, + "required": [ + "name", + "create" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Private": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + }, + "vpcId": { + "type": "string" + } + }, + "required": [ + "name", + "create", + "vpcId" + ] + }, + "Spec.Distribution.Modules.Logging": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "opensearch": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + } + }, + "required": ["opensearch"] + }, + "Spec.Distribution.Modules.Logging.Opensearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "triple"] + }, + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "storageSize": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Monitoring": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "prometheus": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" + }, + "alertmanager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + } + } + }, + "Spec.Distribution.Modules.Monitoring.Prometheus": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "retentionTime": { + "type": "string" + }, + "retentionSize": { + "type": "string" + }, + "storageSize": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Monitoring.AlertManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "deadManSwitchWebhookUrl": { + "type": "string" + }, + "slackWebhookUrl": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "gatekeeper": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" + } + } + }, + "Spec.Distribution.Modules.Policy.Gatekeeper": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalExcludedNamespaces": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Spec.Distribution.Modules.Dr": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "velero": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" + } + }, + "required": ["velero"] + }, + "Spec.Distribution.Modules.Dr.Velero": { + "type": "object", + "additionalProperties": false, + "properties": { + "eks": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + } + }, + "required": ["eks"] + }, + "Spec.Distribution.Modules.Dr.Velero.Eks": { + "type": "object", + "additionalProperties": false, + "properties": { + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "bucketName": { + "type": "string" + }, + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "region", + "bucketName", + "iamRoleArn" + ] + }, + "Spec.Distribution.Modules.Auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" + }, + "pomerium": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" + }, + "dex": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" + } + }, + "required": ["provider"], + "allOf": [ + { + "if": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "sso" + } + } + } + } + }, + "then": { + "properties": { + "auth": { + "required": ["dex", "pomerium"] + } + } + }, + "else": { + "properties": { + "dex": { + "type": "null" + }, + "pomerium": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "basicAuth" + } + } + } + } + }, + "then": { + "properties": { + "provider": { + "required": ["basicAuth"] + } + } + }, + "else": { + "properties": { + "provider": { + "basicAuth": { + "type": "null" + } + } + } + } + } + ] + }, + "Spec.Distribution.Modules.Auth.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingress" + } + } + } + }, + "Spec.Distribution.Modules.Auth.Overrides.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "host", + "ingressClass" + ] + }, + "Spec.Distribution.Modules.Auth.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["none", "basicAuth", "sso"] + }, + "basicAuth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" + }, + "policy": { + "type": "string" + } + }, + "required": [ + "secrets", + "policy" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "COOKIE_SECRET": { + "type": "string" + }, + "IDP_CLIENT_SECRET": { + "type": "string" + }, + "SHARED_SECRET": { + "type": "string" + } + }, + "required": [ + "COOKIE_SECRET", + "IDP_CLIENT_SECRET", + "SHARED_SECRET" + ] + }, + "Spec.Distribution.Modules.Auth.Dex": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectors": { + "type": "array" + } + }, + "required": ["connectors"] + }, + "Spec.Distribution.Modules.Aws": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "clusterAutoscaler": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + }, + "ebsCsiDriver": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn" + ] + }, + "loadBalancerController": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn" + ] + } + } + }, + + "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "iamRoleArn", + "region" + ] + }, + + "Types.SemVer": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "Types.IpAddress": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" + }, + "Types.Cidr": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" + }, + "Types.FileRef": { + "type": "string", + "pattern": "^\\{file\\:\\/\\/.+\\}$" + }, + "Types.EnvRef": { + "type": "string", + "pattern": "\\{^env\\:\\/\\/.*\\}$" + }, + "Types.TcpPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "Types.SshPubKey": { + "type": "string", + "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" + }, + "Types.Uri": { + "type": "string", + "pattern": "^(http|https)\\:\\/\\/.+$" + }, + "Types.AwsArn": { + "type": "string", + "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{17}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", + "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + } + }, + "Types.FuryModuleOverridesIngress": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl.yaml b/test/data/e2e/validate/config/nodistro/furyctl.yaml similarity index 76% rename from test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl.yaml rename to test/data/e2e/validate/config/nodistro/furyctl.yaml index d1cabab7e..f50652a1a 100644 --- a/test/integration/validation-cmd/data/config-valid-furyctl-yaml/furyctl.yaml +++ b/test/data/e2e/validate/config/nodistro/furyctl.yaml @@ -9,7 +9,7 @@ metadata: name: awesome-cluster-staging spec: # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: "v1.23.3" + distributionVersion: v1.24.1 # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: terraform: @@ -76,71 +76,69 @@ spec: - subnet-0ae4e9199d9192226 - subnet-01787e8da51e4f070 apiServerEndpointAccess: - # by default all clusters are created with a private control plane - (needs to be added to the installer's options) - type: "private" - # cidr allowed to talk with the apiServer - allowedCidrs: - - 10.1.0.0/16 - nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" - nodePools: - - name: worker - # optional, was OS, and version was removed - ami: - id: null - owner: null - # sizing, with a more hierarchical structure - size: - min: 1 - max: 3 - instance: - type: t3.micro - # optional, this enable spot instances on the ASG - spot: false - volumeSize: 50 - # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed - attachedTargetGroups: - - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 - - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 - # labels that are added to the nodes - labels: - nodepool: worker - node.kubernetes.io/role: worker - # optional, taints added to the nodes - taints: - - node.kubernetes.io/role=worker:NoSchedule - # tags added to the ASG - tags: - k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" - k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - # additional rules added to the ASG nodes security group - additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 - # aws-auth configmap definiton - awsAuth: - # Additional AWS account id to add to the aws-auth configmap, optional - additionalAccounts: - - "777777777777" - - "88888888888" - # Additional users to add to the aws-auth configmap, optional - users: - - username: "samuele" - groups: - - system:masters - userarn: "arn:aws:iam::363601582189:user/samuele" - # Additional roles to add to the aws-auth configmap, optional - roles: - - username: "sighup-support" - groups: - - sighup-support:masters - rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" - # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + type: private + allowedCidrs: + - 10.0.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + # optional, was OS, and version was removed + ami: + id: null + owner: null + # sizing, with a more hierarchical structure + size: + min: 1 + max: 3 + instance: + type: t3.micro + # optional, this enable spot instances on the ASG + spot: false + volumeSize: 50 + # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + # labels that are added to the nodes + labels: + nodepool: worker + node.kubernetes.io/role: worker + # optional, taints added to the nodes + taints: + - node.kubernetes.io/role=worker:NoSchedule + # tags added to the ASG + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + # additional rules added to the ASG nodes security group + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + # aws-auth configmap definiton + awsAuth: + # Additional AWS account id to add to the aws-auth configmap, optional + additionalAccounts: + - "777777777777" + - "88888888888" + # Additional users to add to the aws-auth configmap, optional + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + # Additional roles to add to the aws-auth configmap, optional + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + # distribution configuration, a subset of the `furyctl-defaults.yaml` file and some additional configurations for prerequisites # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules distribution: common: @@ -190,15 +188,13 @@ spec: # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external type: http01 # if auth type is route53, should be taken automatically from the role generation using module.ingress.dns.public settings - # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns + # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns dns: public: - enabled: true name: "fury-demo.sighup.io" # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created create: false private: - enabled: true name: "internal.fury-demo.sighup.io" # optional, if vpc is enabled: false vpcId: "vpc123123123123" @@ -228,7 +224,7 @@ spec: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: "150Gi" # override ingresses parameters # monitoring module configuration monitoring: @@ -325,4 +321,4 @@ spec: redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug - useLoginAsID: false \ No newline at end of file + useLoginAsID: false diff --git a/test/data/e2e/validate/config/wrong/furyctl-defaults.yaml b/test/data/e2e/validate/config/wrong/furyctl-defaults.yaml new file mode 100644 index 000000000..707d13c4d --- /dev/null +++ b/test/data/e2e/validate/config/wrong/furyctl-defaults.yaml @@ -0,0 +1,223 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: null + tolerations: null + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: example.dev + externalDns: + privateIamRoleArn: arn:aws:iam::123456789012:role/external-dns-private + publicIamRoleArn: arn:aws:iam::123456789012:role/external-dns-public + dns: + public: + name: "" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + # private is used only when ingress.nginx.type is "dual" + private: + # required to be set by the user, ex: internal.fury-demo.sighup.io + name: "" + create: true + # internal field, should be either the VPC ID taken from the kubernetes + # phase or the ID of the created VPC in the Ifra phase + vpcId: "" + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + email: engineering+fury-distribution@sighup.io + # can be dns01 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789012:role/example-route53 + region: eu-west-1 + hostedZoneId: "" + # logging module configuration + logging: + overrides: + nodeSelector: null + tolerations: null + ingresses: + opensearchDashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + minio: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storageSize: 150Gi + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: null + tolerations: null + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + retentionTime: 30d + retentionSize: 120GB + storageSize: 150Gi + alertmanager: + deadManSwitchWebhookUrl: "" + slackWebhookUrl: "" + # policy module configuration + policy: + overrides: + nodeSelector: null + tolerations: null + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: null + tolerations: null + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789012:role/example-velero + region: eu-west-1 + bucketName: example-velero + # auth module configuration + auth: + overrides: + nodeSelector: null + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: null + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + policy: "" + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: "" + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: "" + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: "" + dex: + # see dex documentation for more information + connectors: [] + aws: + overrides: + nodeSelector: null + tolerations: null + ebsCsiDriver: + iamRoleArn: arn:aws:iam::123456789012:role/ebs-csi-controller-role + loadBalancerController: + iamRoleArn: arn:aws:iam::123456789012:role/example-load-balancer-controller + clusterAutoscaler: + region: eu-west-1 + iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl.yaml b/test/data/e2e/validate/config/wrong/furyctl.yaml similarity index 77% rename from test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl.yaml rename to test/data/e2e/validate/config/wrong/furyctl.yaml index 2affdfca1..300b64100 100644 --- a/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/furyctl.yaml +++ b/test/data/e2e/validate/config/wrong/furyctl.yaml @@ -9,7 +9,7 @@ metadata: name: awesome-cluster-staging spec: # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: "v1.23.3" + distributionVersion: v1.24.1 test: test # should make the validation fail # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: @@ -77,71 +77,69 @@ spec: - subnet-0ae4e9199d9192226 - subnet-01787e8da51e4f070 apiServerEndpointAccess: - # by default all clusters are created with a private control plane - (needs to be added to the installer's options) - type: "private" - # cidr allowed to talk with the apiServer + type: private allowedCidrs: - - 10.1.0.0/16 - nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" - nodePools: - - name: worker - # optional, was OS, and version was removed - ami: - id: null - owner: null - # sizing, with a more hierarchical structure - size: - min: 1 - max: 3 - instance: - type: t3.micro - # optional, this enable spot instances on the ASG - spot: false - volumeSize: 50 - # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed - attachedTargetGroups: - - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 - - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 - # labels that are added to the nodes - labels: - nodepool: worker - node.kubernetes.io/role: worker - # optional, taints added to the nodes - taints: - - node.kubernetes.io/role=worker:NoSchedule - # tags added to the ASG - tags: - k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" - k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - # additional rules added to the ASG nodes security group - additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 - # aws-auth configmap definiton - awsAuth: - # Additional AWS account id to add to the aws-auth configmap, optional - additionalAccounts: - - "777777777777" - - "88888888888" - # Additional users to add to the aws-auth configmap, optional - users: - - username: "samuele" - groups: - - system:masters - userarn: "arn:aws:iam::363601582189:user/samuele" - # Additional roles to add to the aws-auth configmap, optional - roles: - - username: "sighup-support" - groups: - - sighup-support:masters - rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" - # distribution configuration, a subset of the `distribution.yaml` file and some additional configurations for prerequisites + - 10.0.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + # optional, was OS, and version was removed + ami: + id: null + owner: null + # sizing, with a more hierarchical structure + size: + min: 1 + max: 3 + instance: + type: t3.micro + # optional, this enable spot instances on the ASG + spot: false + volumeSize: 50 + # optional, this parameter is used when external target groups are attached to the ASG, otherwise everytime furyctl executes the target groups will be removed + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + # labels that are added to the nodes + labels: + nodepool: worker + node.kubernetes.io/role: worker + # optional, taints added to the nodes + taints: + - node.kubernetes.io/role=worker:NoSchedule + # tags added to the ASG + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + # additional rules added to the ASG nodes security group + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + # aws-auth configmap definiton + awsAuth: + # Additional AWS account id to add to the aws-auth configmap, optional + additionalAccounts: + - "777777777777" + - "88888888888" + # Additional users to add to the aws-auth configmap, optional + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + # Additional roles to add to the aws-auth configmap, optional + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + # distribution configuration, a subset of the `furyctl-defaults.yaml` file and some additional configurations for prerequisites # in this phase we are managing the kustomize project AND the terraform prerequisites for the distro modules distribution: common: @@ -194,12 +192,10 @@ spec: # this key will manage the creation of the AWS zones and the iam roles needed for ingress module, eg: certManager and externalDns dns: public: - enabled: true name: "fury-demo.sighup.io" # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created create: false private: - enabled: true name: "internal.fury-demo.sighup.io" # optional, if vpc is enabled: false vpcId: "vpc123123123123" @@ -229,7 +225,7 @@ spec: cpu: "" memory: "" # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storage_request: "" + storageSize: "150Gi" # override ingresses parameters # monitoring module configuration monitoring: diff --git a/test/data/e2e/validate/config/wrong/kfd.yaml b/test/data/e2e/validate/config/wrong/kfd.yaml new file mode 100644 index 000000000..3f88aae50 --- /dev/null +++ b/test/data/e2e/validate/config/wrong/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: 2.8.12 diff --git a/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json new file mode 100644 index 000000000..37842f285 --- /dev/null +++ b/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json @@ -0,0 +1,1569 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["EKSCluster"] + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "$defs": { + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 19 + } + }, + "required": [ + "name" + ] + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "distributionVersion": { + "$ref": "#/$defs/Types.SemVer" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "toolsConfiguration": { + "$ref": "#/$defs/Spec.ToolsConfiguration" + }, + "infrastructure": { + "$ref": "#/$defs/Spec.Infrastructure" + }, + "kubernetes": { + "$ref": "#/$defs/Spec.Kubernetes" + }, + "distribution": { + "$ref": "#/$defs/Spec.Distribution" + } + }, + "required": [ + "distributionVersion", + "region", + "kubernetes", + "distribution", + "toolsConfiguration" + ], + "if": { + "anyOf": [ + { + "properties": { + "infrastructure": { + "type": "null" + } + } + }, + { + "properties": { + "infrastructure": { + "properties": { + "vpc": { + "type": "null" + } + } + } + } + }, + { + "properties": { + "infrastructure": { + "properties": { + "vpc": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "kubernetes": { + "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + } + } + }, + "else": { + "properties": { + "kubernetes": { + "type": "object", + "properties": { + "vpcId": { + "type": "null" + }, + "subnetIds": { + "type": "null" + }, + "apiServerEndpointAccess": { + "type": "null" + } + } + } + } + } + }, + + "Spec.ToolsConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" + } + }, + "required": [ + "terraform" + ] + }, + "Spec.ToolsConfiguration.Terraform": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" + } + }, + "required": [ + "state" + ] + }, + "Spec.ToolsConfiguration.Terraform.State": { + "type": "object", + "additionalProperties": false, + "properties": { + "s3": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" + } + }, + "required": [ + "s3" + ] + }, + "Spec.ToolsConfiguration.Terraform.State.S3": { + "type": "object", + "additionalProperties": false, + "properties": { + "bucketName": { + "type": "string" + }, + "keyPrefix": { + "type": "string", + "maxLength": 37 + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "bucketName", + "keyPrefix", + "region" + ] + }, + + "Spec.Infrastructure": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpc": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc" + } + } + }, + "Spec.Infrastructure.Vpc": { + "type": "object", + "additionalProperties": false, + "properties": { + "network": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" + } + }, + "required": [ + "network" + ] + }, + "Spec.Infrastructure.Vpc.Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "subnetsCidrs": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" + } + }, + "required": [ + "cidr", + "subnetsCidrs" + ] + }, + "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { + "type": "object", + "additionalProperties": false, + "properties": { + "private": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "public": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "private", + "public" + ] + }, + "Spec.Infrastructure.Vpc.Vpn": { + "type": "object", + "additionalProperties": false, + "properties": { + "instances": { + "type": "integer" + }, + "port": { + "$ref": "#/$defs/Types.TcpPort" + }, + "instanceType": { + "type": "string" + }, + "diskSize": { + "type": "integer" + }, + "operatorName": { + "type": "string" + }, + "dhParamsBits": { + "type": "integer" + }, + "vpnClientsSubnetCidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "ssh": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + } + }, + "required": [ + "ssh", + "vpnClientsSubnetCidr" + ] + }, + "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "type": "object", + "additionalProperties": false, + "properties": { + "publicKeys": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + } + }, + "githubUsersName": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedFromCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedFromCidrs", + "githubUsersName", + "publicKeys" + ] + }, + + "Spec.Kubernetes": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "apiServerEndpointAccess": { + "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + }, + "nodeAllowedSshPublicKey": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + }, + "nodePools": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool" + } + }, + "awsAuth": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" + } + }, + "required": [ + "nodeAllowedSshPublicKey", + "nodePools" + ] + }, + "Spec.Kubernetes.APIServerEndpointAccess": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "public", + "private", + "public_and_private" + ] + }, + "allowedCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 1 + } + }, + "required": [ + "allowedCidrs", + "type" + ] + }, + "Spec.Kubernetes.NodePool": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "ami": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" + }, + "size": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" + }, + "instance": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" + }, + "attachedTargetGroups": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "taints": { + "$ref": "#/$defs/Types.KubeTaints" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "additionalFirewallRules": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" + } + } + }, + "required": [ + "ami", + "instance", + "name", + "size" + ] + }, + "Spec.Kubernetes.NodePool.Ami": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "owner" + ] + }, + "Spec.Kubernetes.NodePool.Instance": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "spot": { + "type": "boolean" + }, + "volumeSize": { + "type": "integer" + } + }, + "required": [ + "type" + ] + }, + "Spec.Kubernetes.NodePool.Size": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer", + "minimum": 0 + }, + "max": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "max", + "min" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/$defs/Types.TcpPort" + }, + "to": { + "$ref": "#/$defs/Types.TcpPort" + } + }, + "required": [ + "from", + "to" + ] + }, + "Spec.Kubernetes.AwsAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" + } + } + } + }, + "Spec.Kubernetes.AwsAuth.Role": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "rolearn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "rolearn", + "username" + ] + }, + "Spec.Kubernetes.AwsAuth.User": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "userarn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "userarn", + "username" + ] + }, + + "Spec.Distribution": { + "type": "object", + "additionalProperties": false, + "properties": { + "common": { + "$ref": "#/$defs/Spec.Distribution.Common" + }, + "modules": { + "$ref": "#/$defs/Spec.Distribution.Modules" + } + }, + "required": [ + "modules" + ], + "if": { + "allOf": [ + { + "required": ["common"] + }, + { + "properties": { + "common": { + "required": ["provider"] + } + } + }, + { + "properties": { + "common": { + "properties": { + "provider": { + "required": ["type"] + } + } + } + } + }, + { + "properties": { + "common": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "eks" + } + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "modules": { + "required": ["aws"] + } + } + }, + "else": { + "properties": { + "modules": { + "properties": { + "aws": { + "type": "null" + } + } + } + } + } + }, + "Spec.Distribution.Common": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Common.Provider" + }, + "relativeVendorPath": { + "type": "string" + } + } + }, + "Spec.Distribution.Common.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth" + }, + "aws": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws" + }, + "dr": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr" + }, + "ingress": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" + }, + "logging": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging" + }, + "monitoring": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" + }, + "policy": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy" + } + }, + "required": [ + "dr", + "ingress", + "logging" + ] + }, + "Spec.Distribution.Modules.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "baseDomain": { + "type": "string" + }, + "nginx": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" + }, + "certManager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager" + }, + "dns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" + }, + "externalDns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + } + }, + "required": [ + "baseDomain", + "dns", + "nginx" + ] + }, + "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecastle": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + }, + "Spec.Distribution.Modules.Ingress.Nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "dual"] + }, + "tls": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "enum": ["certManager", "secret", "none"] + }, + "secret": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" + } + }, + "required": [ + "provider" + ], + "if": { + "properties": { + "provider": { + "const": "secret" + } + } + }, + "then": { + "required": ["secret"] + } + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ca": { + "type": "string" + } + }, + "required": [ + "ca", + "cert", + "key" + ] + }, + "Spec.Distribution.Modules.Ingress.CertManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "clusterIssuer": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + } + }, + "required": [ + "clusterIssuer" + ] + }, + "Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "type": { + "type": "string", + "enum": ["dns01", "http01"] + }, + "route53": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" + } + }, + "required": [ + "name", + "type", + "email" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "hostedZoneId": { + "type": "string" + } + }, + "required": [ + "hostedZoneId", + "iamRoleArn", + "region" + ] + }, + "Spec.Distribution.Modules.Ingress.ExternalDNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "privateIamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "publicIamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "privateIamRoleArn", + "publicIamRoleArn" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "public": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" + }, + "private": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + } + }, + "required": [ + "public", + "private" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Public": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + } + }, + "required": [ + "name", + "create" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Private": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + }, + "vpcId": { + "type": "string" + } + }, + "required": [ + "name", + "create", + "vpcId" + ] + }, + "Spec.Distribution.Modules.Logging": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "opensearch": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + } + }, + "required": ["opensearch"] + }, + "Spec.Distribution.Modules.Logging.Opensearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "triple"] + }, + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "storageSize": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Monitoring": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "prometheus": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" + }, + "alertmanager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + } + } + }, + "Spec.Distribution.Modules.Monitoring.Prometheus": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "retentionTime": { + "type": "string" + }, + "retentionSize": { + "type": "string" + }, + "storageSize": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Monitoring.AlertManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "deadManSwitchWebhookUrl": { + "type": "string" + }, + "slackWebhookUrl": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "gatekeeper": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" + } + } + }, + "Spec.Distribution.Modules.Policy.Gatekeeper": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalExcludedNamespaces": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Spec.Distribution.Modules.Dr": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "velero": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" + } + }, + "required": ["velero"] + }, + "Spec.Distribution.Modules.Dr.Velero": { + "type": "object", + "additionalProperties": false, + "properties": { + "eks": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + } + }, + "required": ["eks"] + }, + "Spec.Distribution.Modules.Dr.Velero.Eks": { + "type": "object", + "additionalProperties": false, + "properties": { + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "bucketName": { + "type": "string" + }, + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "region", + "bucketName", + "iamRoleArn" + ] + }, + "Spec.Distribution.Modules.Auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" + }, + "pomerium": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" + }, + "dex": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" + } + }, + "required": ["provider"], + "allOf": [ + { + "if": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "sso" + } + } + } + } + }, + "then": { + "properties": { + "auth": { + "required": ["dex", "pomerium"] + } + } + }, + "else": { + "properties": { + "dex": { + "type": "null" + }, + "pomerium": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "basicAuth" + } + } + } + } + }, + "then": { + "properties": { + "provider": { + "required": ["basicAuth"] + } + } + }, + "else": { + "properties": { + "provider": { + "basicAuth": { + "type": "null" + } + } + } + } + } + ] + }, + "Spec.Distribution.Modules.Auth.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingress" + } + } + } + }, + "Spec.Distribution.Modules.Auth.Overrides.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "host", + "ingressClass" + ] + }, + "Spec.Distribution.Modules.Auth.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["none", "basicAuth", "sso"] + }, + "basicAuth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" + }, + "policy": { + "type": "string" + } + }, + "required": [ + "secrets", + "policy" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "COOKIE_SECRET": { + "type": "string" + }, + "IDP_CLIENT_SECRET": { + "type": "string" + }, + "SHARED_SECRET": { + "type": "string" + } + }, + "required": [ + "COOKIE_SECRET", + "IDP_CLIENT_SECRET", + "SHARED_SECRET" + ] + }, + "Spec.Distribution.Modules.Auth.Dex": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectors": { + "type": "array" + } + }, + "required": ["connectors"] + }, + "Spec.Distribution.Modules.Aws": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "clusterAutoscaler": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + }, + "ebsCsiDriver": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn" + ] + }, + "loadBalancerController": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn" + ] + } + } + }, + + "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "iamRoleArn", + "region" + ] + }, + + "Types.SemVer": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "Types.IpAddress": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" + }, + "Types.Cidr": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" + }, + "Types.FileRef": { + "type": "string", + "pattern": "^\\{file\\:\\/\\/.+\\}$" + }, + "Types.EnvRef": { + "type": "string", + "pattern": "\\{^env\\:\\/\\/.*\\}$" + }, + "Types.TcpPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "Types.SshPubKey": { + "type": "string", + "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" + }, + "Types.Uri": { + "type": "string", + "pattern": "^(http|https)\\:\\/\\/.+$" + }, + "Types.AwsArn": { + "type": "string", + "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{17}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", + "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + } + }, + "Types.FuryModuleOverridesIngress": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/test/data/e2e/validate/dependencies/correct/furyagent/0.3.0/furyagent b/test/data/e2e/validate/dependencies/correct/furyagent/0.3.0/furyagent new file mode 100755 index 000000000..71bda34a8 --- /dev/null +++ b/test/data/e2e/validate/dependencies/correct/furyagent/0.3.0/furyagent @@ -0,0 +1,5 @@ +#!/bin/sh + +cat <[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{17}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", + "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + } + }, + "Types.FuryModuleOverridesIngress": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/test/data/expensive/common/data/templates/distribution/.gitignore.tpl b/test/data/expensive/common/data/templates/distribution/.gitignore.tpl new file mode 100644 index 000000000..1d085cacc --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/.gitignore.tpl @@ -0,0 +1 @@ +** diff --git a/test/data/expensive/common/data/templates/distribution/_helpers.tpl b/test/data/expensive/common/data/templates/distribution/_helpers.tpl new file mode 100644 index 000000000..f1e63782f --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/_helpers.tpl @@ -0,0 +1,91 @@ +{{ define "commonNodeSelector" }} + {{- $indent := .indent | default 8 -}} + {{ .spec.distribution.common.nodeSelector | toYaml | indent $indent | trim }} +{{- end -}} + +{{ define "commonTolerations" }} + {{- $indent := .indent | default 8 -}} + {{ .spec.distribution.common.tolerations | toYaml | indent $indent | trim }} +{{- end -}} + +{{ define "globalIngressClass" }} + {{- if eq .spec.distribution.modules.ingress.nginx.type "single" -}} + "nginx" + {{- else -}} + {{ .type }} + {{- end -}} +{{ end }} + +{{/* ingressClass { module: , package: , type: "internal|external", spec: "." } */}} +{{ define "ingressClass" }} + {{- $module := index .spec.distribution.modules .module -}} + {{- $package := index $module.overrides.ingresses .package -}} + {{- $ingressClass := $package.ingressClass -}} + {{- if $ingressClass -}} + {{ $ingressClass }} + {{- else -}} + {{ template "globalIngressClass" (dict "spec" .spec "type" .type) }} + {{- end -}} +{{ end }} + +{{/* ingressHost { module: , package: , prefix: , spec: "." } */}} +{{ define "ingressHost" }} + {{- $module := index .spec.distribution.modules .module -}} + {{- $package := index $module.overrides.ingresses .package -}} + {{- $host := $package.host -}} + {{- if $host -}} + {{ $host }} + {{- else -}} + {{ print .prefix .spec.distribution.modules.ingress.baseDomain }} + {{- end -}} +{{ end }} + +{{/* ingressTls { module: , package: , prefix: , spec: "." } */}} +{{- define "ingressTls" -}} +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "none" -}} + {{ else }} + tls: + - hosts: + - {{ template "ingressHost" . }} + {{- if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" }} + secretName: {{ lower .package }}-tls + {{- end }} +{{- end }} +{{- end -}} + +{{ define "pomeriumHost" }} + {{- template "ingressHost" (dict "module" "auth" "package" "pomerium" "prefix" "pomerium.internal." "spec" .spec) -}} +{{ end }} + +{{ define "ingressAuthUrl" -}} +"https://{{ template "pomeriumHost" . }}/verify?uri=$scheme://$host$request_uri" +{{- end }} + +{{ define "ingressAuthSignin" -}} +"https://{{ template "pomeriumHost" . }}/?uri=$scheme://$host$request_uri" +{{- end }} + +{{ define "ingressAuth" }} +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} + nginx.ingress.kubernetes.io/auth-url: {{ template "ingressAuthUrl" . }} + nginx.ingress.kubernetes.io/auth-signin: {{ template "ingressAuthSignin" . }} +{{- else if eq .spec.distribution.modules.auth.provider.type "basicAuth" -}} + nginx.ingress.kubernetes.io/auth-type: basic + nginx.ingress.kubernetes.io/auth-secret: basic-auth + nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required' +{{- end -}} +{{ end }} + +{{ define "certManagerClusterIssuer" }} +{{- if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" -}} +cert-manager.io/cluster-issuer: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.name }} +{{- end -}} +{{ end }} + +{{ define "alertmanagerUrl" }} + {{- template "ingressHost" (dict "module" "monitoring" "package" "alertmanager" "prefix" "alertmanager.internal." "spec" .) -}} +{{ end }} + +{{ define "prometheusUrl" }} + {{- template "ingressHost" (dict "module" "monitoring" "package" "prometheus" "prefix" "prometheus.internal." "spec" .) -}} +{{ end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/kustomization.yaml.tpl new file mode 100644 index 000000000..21586e6d7 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/kustomization.yaml.tpl @@ -0,0 +1,42 @@ +{{- if eq .spec.distribution.modules.auth.provider.type "none" -}} +{{- else -}} +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +{{- end -}} +{{- if eq .spec.distribution.modules.auth.provider.type "sso" }} + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/auth/katalog/dex" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/auth/katalog/pomerium" }} + - resources/ingress-infra.yml + +patchesStrategicMerge: + - patches/infra-nodes.yml + - patches/pomerium-ingress.yml + +configMapGenerator: + - name: pomerium + behavior: replace + envs: + - resources/pomerium-config.env + - name: pomerium-policy + behavior: replace + files: + - policy.yml=resources/pomerium-policy.yml + +secretGenerator: + - name: dex + namespace: kube-system + files: + - config.yml=secrets/dex.yml + - name: pomerium-env + behavior: replace + envs: + - secrets/pomerium.env +{{- end -}} +{{- if eq .spec.distribution.modules.auth.provider.type "basicAuth" }} + +resources: + - secrets/basic-auth.yml +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..56e1e1108 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/patches/infra-nodes.yml.tpl @@ -0,0 +1,50 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.auth.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.auth.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.auth.overrides.tolerations nil -}} + {{ .spec.distribution.modules.auth.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dex + namespace: kube-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pomerium + namespace: pomerium +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/patches/pomerium-ingress.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/patches/pomerium-ingress.yml.tpl new file mode 100644 index 000000000..0cc5499cc --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/patches/pomerium-ingress.yml.tpl @@ -0,0 +1,26 @@ +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + {{- if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" }} + annotations: + {{ template "certManagerClusterIssuer" . }} + {{- end }} + name: pomerium + namespace: pomerium +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "auth" "package" "pomerium" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "pomeriumHost" . }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: pomerium + port: + number: 80 +{{- template "ingressTls" (dict "module" "auth" "package" "pomerium" "prefix" "pomerium.internal." "spec" .spec) }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/ingress-infra.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/ingress-infra.yml.tpl new file mode 100644 index 000000000..ea2fd06d2 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/ingress-infra.yml.tpl @@ -0,0 +1,27 @@ +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + {{- if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" }} + annotations: + {{ template "certManagerClusterIssuer" . }} + {{- end }} + name: dex + namespace: kube-system +spec: + # Needs to be externally available in order to act as callback from GitHub. + ingressClassName: {{ template "ingressClass" (dict "module" "auth" "package" "dex" "type" "external" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "auth" "package" "dex" "prefix" "login." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: dex + port: + name: http +{{- template "ingressTls" (dict "module" "auth" "package" "dex" "prefix" "login." "spec" .spec) }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-config.env.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-config.env.tpl new file mode 100644 index 000000000..54662da89 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-config.env.tpl @@ -0,0 +1,31 @@ +{{- define "pomeriumHost" -}} + {{ if .spec.distribution.modules.auth.overrides.ingresses.pomerium.host -}} + {{ .spec.distribution.modules.auth.overrides.ingresses.pomerium.host }} + {{- else -}} + {{ print "pomerium.internal." .spec.distribution.modules.ingress.baseDomain }} + {{- end }} +{{- end -}} +{{- define "dexHost" -}} + {{ if .spec.distribution.modules.auth.overrides.ingresses.dex.host -}} + {{ print "https://" .spec.distribution.modules.auth.overrides.ingresses.dex.host }} + {{- else -}} + {{ print "https://login" .spec.distribution.modules.ingress.baseDomain }} + {{- end }} +{{- end -}} +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +FORWARD_AUTH_HOST={{ template "pomeriumHost" . }} +AUTHENTICATE_SERVICE_HOST={{ template "pomeriumHost" . }} +FORWARD_AUTH_URL=https://$(FORWARD_AUTH_HOST) +AUTHENTICATE_SERVICE_URL=https://$(AUTHENTICATE_SERVICE_HOST) +AUTOCERT=false +POMERIUM_DEBUG=true +LOG_LEVEL=debug + +# DEX settings +IDP_CLIENT_ID=pomerium +# See https://docs.pomerium.io/configuration/#identity-provider-name +IDP_PROVIDER=oidc +# IDP_PROVIDER_URL is the url of dex ingress +IDP_PROVIDER_URL={{ template "dexHost" . }} +IDP_SCOPES=openid profile email groups +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-policy.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-policy.yml.tpl new file mode 100644 index 000000000..1bb249206 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/resources/pomerium-policy.yml.tpl @@ -0,0 +1,3 @@ +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +{{ .spec.distribution.modules.auth.pomerium.policy }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/basic-auth.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/basic-auth.yml.tpl new file mode 100644 index 000000000..f88939459 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/basic-auth.yml.tpl @@ -0,0 +1,16 @@ +{{- if eq .spec.distribution.modules.auth.provider.type "basicAuth" -}} +{{- $username := .spec.distribution.modules.auth.provider.basicAuth.username -}} +{{- $password := .spec.distribution.modules.auth.provider.basicAuth.password -}} +{{- $namespaces := list "gatekeeper-system" "ingress-nginx" "logging" "monitoring" -}} +{{ range $namespace := $namespaces -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: basic-auth + namespace: {{ $namespace }} +type: kubernetes.io/basic-auth +data: + auth: {{ htpasswd $username $password }} +{{ end }} +{{- end -}} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/dex.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/dex.yml.tpl new file mode 100644 index 000000000..a89d584d2 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/dex.yml.tpl @@ -0,0 +1,36 @@ +{{- define "dexHost" -}} + {{ if .spec.distribution.modules.auth.overrides.ingresses.dex.host -}} + {{ print "https://" .spec.distribution.modules.auth.overrides.ingresses.dex.host }} + {{- else -}} + {{ print "https://login." .spec.distribution.modules.ingress.baseDomain }} + {{- end }} +{{- end -}} +{{- define "pomeriumHost" }} + {{- if .spec.distribution.modules.auth.overrides.ingresses.pomerium.host -}} + {{ print "https://" .spec.distribution.modules.auth.overrides.ingresses.pomerium.host "/oauth2/callback" }} + {{- else -}} + {{ print "https://pomerium.internal." .spec.distribution.modules.ingress.baseDomain "/oauth2/callback" }} + {{- end }} +{{- end -}} +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +issuer: {{ template "dexHost" . }} +storage: + type: kubernetes + config: + inCluster: true +web: + http: 0.0.0.0:5556 +telemetry: + http: 0.0.0.0:5558 +connectors: +{{ .spec.distribution.modules.auth.dex.connectors | toYaml | indent 2 }} +oauth2: + skipApprovalScreen: true +staticClients: +- id: pomerium + redirectURIs: + - {{ template "pomeriumHost" . }} + name: 'Pomerium in-cluster SSO' + secret: {{ .spec.distribution.modules.auth.pomerium.secrets.IDP_CLIENT_SECRET }} +enablePasswordDB: false +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/pomerium.env.tpl b/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/pomerium.env.tpl new file mode 100644 index 000000000..a3685a899 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/auth/secrets/pomerium.env.tpl @@ -0,0 +1,8 @@ +{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} +# COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret +COOKIE_SECRET={{ .spec.distribution.modules.auth.pomerium.secrets.COOKIE_SECRET }} +#IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client +IDP_CLIENT_SECRET={{ .spec.distribution.modules.auth.pomerium.secrets.IDP_CLIENT_SECRET }} +# SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret +SHARED_SECRET={{ .spec.distribution.modules.auth.pomerium.secrets.SHARED_SECRET }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/aws/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/aws/kustomization.yaml.tpl new file mode 100644 index 000000000..cb7ed39b3 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/aws/kustomization.yaml.tpl @@ -0,0 +1,18 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +{{- $version := semver .spec.distributionVersion }} + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/aws/katalog/cluster-autoscaler/v" $version.Major "." $version.Minor ".x" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/aws/katalog/ebs-csi-driver" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/aws/katalog/load-balancer-controller" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/aws/katalog/node-termination-handler" }} + - resources/sc.yml + +patchesStrategicMerge: + - patches/cluster-autoscaler.yml + - patches/ebs-csi-driver.yml + - patches/infra-nodes.yml + - patches/load-balancer-controller.yml diff --git a/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/cluster-autoscaler.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/cluster-autoscaler.yml.tpl new file mode 100644 index 000000000..46f63eb8e --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/cluster-autoscaler.yml.tpl @@ -0,0 +1,24 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-autoscaler + namespace: kube-system +spec: + template: + spec: + containers: + - name: aws-cluster-autoscaler + env: + - name: AWS_REGION + value: {{ .spec.distribution.modules.aws.clusterAutoscaler.region }} + - name: CLUSTER_NAME + value: {{ .metadata.name }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.aws.clusterAutoscaler.iamRoleArn }} + name: cluster-autoscaler + namespace: kube-system diff --git a/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/ebs-csi-driver.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/ebs-csi-driver.yml.tpl new file mode 100644 index 000000000..28e59beca --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/ebs-csi-driver.yml.tpl @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.aws.ebsCsiDriver.iamRoleArn }} + name: ebs-csi-controller-sa + namespace: kube-system diff --git a/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..64dd84045 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/infra-nodes.yml.tpl @@ -0,0 +1,74 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.aws.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.aws.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.aws.overrides.tolerations nil -}} + {{ .spec.distribution.modules.aws.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: aws-load-balancer-controller + namespace: kube-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-autoscaler + namespace: kube-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ebs-csi-controller + namespace: kube-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: snapshot-controller + namespace: kube-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/load-balancer-controller.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/load-balancer-controller.yml.tpl new file mode 100644 index 000000000..8741d0862 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/aws/patches/load-balancer-controller.yml.tpl @@ -0,0 +1,22 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: aws-load-balancer-controller + namespace: kube-system +spec: + template: + spec: + containers: + - name: controller + env: + - name: CLUSTER_NAME + value: {{ .metadata.name }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.aws.loadBalancerController.iamRoleArn }} + name: aws-load-balancer-controller + namespace: kube-system diff --git a/test/data/expensive/common/data/templates/distribution/manifests/aws/resources/sc.yml b/test/data/expensive/common/data/templates/distribution/manifests/aws/resources/sc.yml new file mode 100644 index 000000000..dd3852b2a --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/aws/resources/sc.yml @@ -0,0 +1,30 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + annotations: + storageclass.kubernetes.io/is-default-class: "true" + name: ebs-sc +parameters: + fsType: ext4 + type: gp2 +provisioner: ebs.csi.aws.com +reclaimPolicy: Delete +volumeBindingMode: WaitForFirstConsumer +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + annotations: + storageclass.kubernetes.io/is-default-class: "false" + name: gp2 +parameters: + fsType: ext4 + type: gp2 +provisioner: kubernetes.io/aws-ebs +reclaimPolicy: Delete +volumeBindingMode: WaitForFirstConsumer diff --git a/test/data/expensive/common/data/templates/distribution/manifests/dr/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/dr/kustomization.yaml.tpl new file mode 100644 index 000000000..f2cae4e34 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/dr/kustomization.yaml.tpl @@ -0,0 +1,13 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/dr/katalog/velero/velero-aws" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/dr/katalog/velero/velero-schedules" }} + - resources/velero-backupstoragelocation.yml + - resources/velero-volumesnapshotlocation.yml + +patchesStrategicMerge: + - patches/infra-nodes.yml + - patches/velero.yml diff --git a/test/data/expensive/common/data/templates/distribution/manifests/dr/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/dr/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..fec139181 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/dr/patches/infra-nodes.yml.tpl @@ -0,0 +1,35 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.dr.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.dr.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.dr.overrides.tolerations nil -}} + {{ .spec.distribution.modules.dr.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: velero + namespace: kube-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/dr/patches/velero.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/dr/patches/velero.yml.tpl new file mode 100644 index 000000000..445d3863e --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/dr/patches/velero.yml.tpl @@ -0,0 +1,26 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.dr.velero.eks.iamRoleArn }} + name: velero + namespace: kube-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: velero + namespace: kube-system +spec: + template: + spec: + containers: + - name: velero + volumeMounts: + - name: cloud-credentials + mountPath: /credentials + $patch: delete + volumes: + - name: cloud-credentials + $patch: delete diff --git a/test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-backupstoragelocation.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-backupstoragelocation.yml.tpl new file mode 100644 index 000000000..22226c70f --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-backupstoragelocation.yml.tpl @@ -0,0 +1,12 @@ +--- +apiVersion: velero.io/v1 +kind: BackupStorageLocation +metadata: + name: default + namespace: kube-system +spec: + provider: velero.io/aws + objectStorage: + bucket: {{ .spec.distribution.modules.dr.velero.eks.bucketName }} + config: + region: {{ .spec.distribution.modules.dr.velero.eks.region }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-volumesnapshotlocation.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-volumesnapshotlocation.yml.tpl new file mode 100644 index 000000000..f057a7f94 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/dr/resources/velero-volumesnapshotlocation.yml.tpl @@ -0,0 +1,10 @@ +--- +apiVersion: velero.io/v1 +kind: VolumeSnapshotLocation +metadata: + name: default + namespace: kube-system +spec: + provider: velero.io/aws + config: + region: {{ .spec.distribution.modules.dr.velero.eks.region }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/kustomization.yaml.tpl new file mode 100644 index 000000000..53dcc25c2 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/kustomization.yaml.tpl @@ -0,0 +1,79 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +{{- if eq .spec.distribution.modules.ingress.nginx.type "dual" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/dual-nginx" }} +{{- else if eq .spec.distribution.modules.ingress.nginx.type "single" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/nginx" }} +{{- end }} +{{- if eq .spec.distribution.modules.ingress.nginx.type "dual" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/external-dns/private" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/external-dns/public" }} +{{- else if eq .spec.distribution.modules.ingress.nginx.type "single" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/external-dns/public" }} +{{- end }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/forecastle" }} +{{- if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/ingress/katalog/cert-manager" }} + - resources/cert-manager-clusterissuer.yml +{{- end }} + - resources/ingress-infra.yml + +patchesStrategicMerge: +{{- if and (eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager") (eq .spec.distribution.modules.ingress.certManager.clusterIssuer.type "dns01") }} + - patches/cert-manager.yml +{{- end }} + - patches/infra-nodes.yml +{{- if eq .spec.distribution.modules.ingress.nginx.type "dual" }} + - patches/ingress-nginx-external.yml + - patches/ingress-nginx-internal.yml +{{- else if eq .spec.distribution.modules.ingress.nginx.type "single" }} + - patches/ingress-nginx.yml +{{- end }} + - patches/external-dns.yml + +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" -}} +patchesJson6902: + - target: + group: apps + version: v1 + kind: Deployment + name: cert-manager + namespace: cert-manager + patch: |- + - op: add + path: /spec/template/spec/containers/0/args/- + value: "--dns01-recursive-nameservers-only" + - op: add + path: /spec/template/spec/containers/0/args/- + value: "--dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53" +{{- end }} +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "secret" -}} +patchesJson6902: + {{- if eq .spec.distribution.modules.ingress.nginx.type "dual" -}} + - target: + group: apps + version: v1 + kind: DaemonSet + name: nginx-ingress-controller-external + namespace: ingress-nginx + path: patchesJson/ingress-nginx.yml + - target: + group: apps + version: v1 + kind: DaemonSet + name: nginx-ingress-controller-internal + namespace: ingress-nginx + path: patchesJson/ingress-nginx.yml + {{- else if .spec.distribution.modules.ingress.nginx.type "single" -}} + - target: + group: apps + version: v1 + kind: DaemonSet + name: nginx-ingress-controller + namespace: ingress-nginx + path: patchesJson/ingress-nginx.yml + {{- end -}} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/cert-manager.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/cert-manager.yml.tpl new file mode 100644 index 000000000..e14253dcc --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/cert-manager.yml.tpl @@ -0,0 +1,10 @@ +{{ if and (eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager") (eq .spec.distribution.modules.ingress.certManager.clusterIssuer.type "dns01") -}} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.route53.iamRoleArn }} + name: cert-manager + namespace: cert-manager +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/external-dns.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/external-dns.yml.tpl new file mode 100644 index 000000000..e8420fe12 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/external-dns.yml.tpl @@ -0,0 +1,90 @@ +{{- if eq .spec.distribution.modules.ingress.nginx.type "dual" }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.ingress.externalDns.publicIamRoleArn }} + name: external-dns-public + namespace: ingress-nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns-public + namespace: ingress-nginx +spec: + template: + spec: + containers: + - name: external-dns + env: + - name: PROVIDER + value: aws + args: + - --source=service + - --source=ingress + - --provider=$(PROVIDER) + - --aws-zone-type=public + - --txt-owner-id={{ .metadata.name}}-public + - --exclude-domains={{ .spec.distribution.modules.ingress.baseDomain }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.ingress.externalDns.privateIamRoleArn }} + name: external-dns-private + namespace: ingress-nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns-private + namespace: ingress-nginx +spec: + template: + spec: + containers: + - name: external-dns + env: + - name: PROVIDER + value: aws + args: + - --source=service + - --source=ingress + - --provider=$(PROVIDER) + - --aws-zone-type=private + - --txt-owner-id={{ .metadata.name}}-private + +{{- end }} +{{- if eq .spec.distribution.modules.ingress.nginx.type "single" }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: {{ .spec.distribution.modules.ingress.externalDns.publicIamRoleArn }} + name: external-dns-public + namespace: ingress-nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns-public + namespace: ingress-nginx +spec: + template: + spec: + containers: + - name: external-dns + env: + - name: PROVIDER + value: aws + args: + - --source=service + - --source=ingress + - --provider=$(PROVIDER) + - --aws-zone-type=public + - --txt-owner-id={{ .metadata.name}}-public +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..709ad7e4f --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/infra-nodes.yml.tpl @@ -0,0 +1,161 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.ingress.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.ingress.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.ingress.overrides.tolerations nil -}} + {{ .spec.distribution.modules.ingress.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager + namespace: cert-manager +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager-cainjector + namespace: cert-manager +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager-webhook + namespace: cert-manager +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{- end }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forecastle + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{ if eq .spec.distribution.modules.ingress.nginx.type "dual" -}} +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-controller-external + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-controller-internal + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{- else if eq .spec.distribution.modules.ingress.nginx.type "single" -}} +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-controller + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{- end }} + +{{ if eq .spec.distribution.modules.ingress.nginx.type "dual" -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns-public + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns-private + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{- else if eq .spec.distribution.modules.ingress.nginx.type "single" -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns-public + namespace: ingress-nginx +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-external.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-external.yml.tpl new file mode 100644 index 000000000..c320d5bad --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-external.yml.tpl @@ -0,0 +1,24 @@ +{{ if eq .spec.distribution.modules.ingress.nginx.type "dual" -}} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing + service.beta.kubernetes.io/aws-load-balancer-type: "external" + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "instance" + service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" + name: ingress-nginx-external + namespace: ingress-nginx +spec: + type: LoadBalancer + externalTrafficPolicy: Cluster +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-configuration-external + namespace: ingress-nginx +data: + use-proxy-protocol: "true" +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-internal.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-internal.yml.tpl new file mode 100644 index 000000000..942798794 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx-internal.yml.tpl @@ -0,0 +1,23 @@ +{{ if eq .spec.distribution.modules.ingress.nginx.type "dual" -}} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "external" + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "instance" + service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" + name: ingress-nginx-internal + namespace: ingress-nginx +spec: + type: LoadBalancer + externalTrafficPolicy: Cluster +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-configuration-internal + namespace: ingress-nginx +data: + use-proxy-protocol: "true" +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx.yml.tpl new file mode 100644 index 000000000..c8e0793b8 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patches/ingress-nginx.yml.tpl @@ -0,0 +1,12 @@ +{{ if eq .spec.distribution.modules.ingress.nginx.type "single" -}} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: nlb + name: ingress-nginx + namespace: ingress-nginx +spec: + type: LoadBalancer +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/patchesJson/ingress-nginx.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patchesJson/ingress-nginx.yml.tpl new file mode 100644 index 000000000..2accba0b9 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/patchesJson/ingress-nginx.yml.tpl @@ -0,0 +1,5 @@ +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "secret" -}} +- op: add + path: /spec/template/spec/containers/0/args/- + value: "--default-ssl-certificate=ingress-nginx/ingress-nginx-global-tls-cert" +{{ end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/cert-manager-clusterissuer.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/cert-manager-clusterissuer.yml.tpl new file mode 100644 index 000000000..b66b2ae9d --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/cert-manager-clusterissuer.yml.tpl @@ -0,0 +1,24 @@ +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" -}} +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.name }} +spec: + acme: + email: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.email }} + privateKeySecretRef: + name: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.name }} + server: https://acme-v02.api.letsencrypt.org/directory + solvers: +{{- if eq .spec.distribution.modules.ingress.certManager.clusterIssuer.type "dns01" }} + - dns01: + route53: + region: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.route53.region }} + hostedZoneID: {{ .spec.distribution.modules.ingress.certManager.clusterIssuer.route53.hostedZoneId }} +{{ else if eq .spec.distribution.modules.ingress.certManager.clusterIssuer.type "http01" }} + - http01: + ingress: + class: {{ template "globalIngressClass" (dict "type" "external" "spec" .spec) }} +{{- end -}} +{{- end -}} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/ingress-infra.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/ingress-infra.yml.tpl new file mode 100644 index 000000000..8c3414aa7 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/resources/ingress-infra.yml.tpl @@ -0,0 +1,26 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Forecastle" + forecastle.stakater.com/icon: "https://raw.githubusercontent.com/stakater/Forecastle/master/assets/web/forecastle-round-100px.png" + {{ if not .spec.distribution.modules.ingress.overrides.ingresses.forecastle.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: forecastle + namespace: ingress-nginx +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "ingress" "package" "forecastle" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "ingress" "package" "forecastle" "prefix" "directory.internal." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: forecastle + port: + name: http +{{- template "ingressTls" (dict "module" "ingress" "package" "forecastle" "prefix" "directory.internal." "spec" .spec) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/.gitignore b/test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/.gitignore new file mode 100644 index 000000000..0b7faa963 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/.gitignore @@ -0,0 +1 @@ +pomerium.env diff --git a/test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/tls.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/tls.yml.tpl new file mode 100644 index 000000000..3b30c03d2 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/ingress/secrets/tls.yml.tpl @@ -0,0 +1,13 @@ +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "secret" -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: ingress-nginx-global-tls-cert + namespace: ingress-nginx +type: kubernetes.io/tls +data: + ca.crt: {{ .spec.distribution.modules.ingress.nginx.tls.secret.ca | b64enc }} + tls.crt: {{ .spec.distribution.modules.ingress.nginx.tls.secret.cert | b64enc }} + tls.key: {{ .spec.distribution.modules.ingress.nginx.tls.secret.key | b64enc }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/kustomization.yaml.tpl new file mode 100644 index 000000000..68667bb9b --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/kustomization.yaml.tpl @@ -0,0 +1,17 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +{{- if ne .spec.distribution.modules.auth.provider.type "none" }} + - auth +{{- end }} +{{- if eq .spec.distribution.common.provider.type "eks" }} + - aws +{{- end }} + - dr + - ingress + - logging + - monitoring + - networking + - opa diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/kustomization.yaml.tpl new file mode 100644 index 000000000..edfe45fb4 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/kustomization.yaml.tpl @@ -0,0 +1,40 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/cerebro" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/configs" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/logging-operated" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/logging-operator" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/opensearch-dashboards" }} +{{- if eq .spec.distribution.modules.logging.opensearch.type "single" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/opensearch-single" }} +{{- else if eq .spec.distribution.modules.logging.opensearch.type "triple" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/logging/katalog/opensearch-triple" }} +{{- end }} + - resources/ingress-infra.yml + +patchesStrategicMerge: + - patches/opensearch.yml + - patches/infra-nodes.yml + +{{ if eq .spec.distribution.modules.ingress.nginx.type "single" -}} +secretGenerator: + - name: kubernetes-index-template + behavior: replace + files: + - kubernetes-index-template=patches/kubernetes-index-template.json + - name: events-index-template + behavior: replace + files: + - events-index-template=patches/events-index-template.json + - name: ingress-controller-index-template + behavior: replace + files: + - ingress-controller-index-template=patches/ingress-controller-index-template.json + - name: systemd-index-template + behavior: replace + files: + - systemd-index-template=patches/systemd-index-template.json +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/events-index-template.json.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/events-index-template.json.tpl new file mode 100644 index 000000000..7bb18befb --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/events-index-template.json.tpl @@ -0,0 +1,10 @@ +{{- if eq .spec.distribution.modules.logging.opensearch.type "single" -}} +{ + "index_patterns" : ["events-*"], + "settings": { + "number_of_shards": 1, + "number_of_replicas": 0, + "codec": "best_compression" + } +} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..78c77fd9e --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/infra-nodes.yml.tpl @@ -0,0 +1,85 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.logging.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.logging.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.logging.overrides.tolerations nil -}} + {{ .spec.distribution.modules.logging.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cerebro + namespace: logging +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: opensearch-cluster-master + namespace: logging +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: opensearch-dashboards + namespace: logging +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: minio + namespace: logging +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: logging.banzaicloud.io/v1beta1 +kind: Logging +metadata: + name: infra +spec: + fluentd: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec "indent" 6 ) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/ingress-controller-index-template.json.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/ingress-controller-index-template.json.tpl new file mode 100644 index 000000000..14562bcb1 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/ingress-controller-index-template.json.tpl @@ -0,0 +1,10 @@ +{{- if eq .spec.distribution.modules.logging.opensearch.type "single" -}} +{ + "index_patterns" : ["ingress-controller-*"], + "settings": { + "number_of_shards": 1, + "number_of_replicas": 0, + "codec": "best_compression" + } +} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/kubernetes-index-template.json.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/kubernetes-index-template.json.tpl new file mode 100644 index 000000000..6bf82531a --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/kubernetes-index-template.json.tpl @@ -0,0 +1,10 @@ +{{- if eq .spec.distribution.modules.logging.opensearch.type "single" -}} +{ + "index_patterns" : ["kubernetes-*"], + "settings": { + "number_of_shards": 1, + "number_of_replicas": 0, + "codec": "best_compression" + } +} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/opensearch.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/opensearch.yml.tpl new file mode 100644 index 000000000..d347ca3d3 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/opensearch.yml.tpl @@ -0,0 +1,24 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: opensearch-cluster-master + namespace: logging +spec: + template: + spec: + containers: + - name: opensearch + {{- if hasKeyAny .spec.distribution.modules.logging.opensearch "resources" }} + resources: + {{ .spec.distribution.modules.logging.opensearch.resources | toYaml | indent 10 | trim }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: opensearch-cluster-master + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .spec.distribution.modules.logging.opensearch.storageSize }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/systemd-index-template.json.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/systemd-index-template.json.tpl new file mode 100644 index 000000000..ec125f6d5 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/patches/systemd-index-template.json.tpl @@ -0,0 +1,10 @@ +{{- if eq .spec.distribution.modules.logging.opensearch.type "single" -}} +{ + "index_patterns" : ["systemd-*"], + "settings": { + "number_of_shards": 1, + "number_of_replicas": 0, + "codec": "best_compression" + } +} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/logging/resources/ingress-infra.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/logging/resources/ingress-infra.yml.tpl new file mode 100644 index 000000000..39af9ee1d --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/logging/resources/ingress-infra.yml.tpl @@ -0,0 +1,78 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Cerebro" + forecastle.stakater.com/icon: "https://github.com/stakater/ForecastleIcons/raw/master/cerebro.png" + {{ if not .spec.distribution.modules.logging.overrides.ingresses.cerebro.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: cerebro + namespace: logging +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "logging" "package" "cerebro" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "logging" "package" "cerebro" "prefix" "cerebro.internal." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: cerebro + port: + name: http +{{- template "ingressTls" (dict "module" "logging" "package" "cerebro" "prefix" "cerebro.internal." "spec" .spec) }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Opensearch Dashboards" + forecastle.stakater.com/icon: "https://opensearch.org/assets/brand/PNG/Mark/opensearch_mark_default.png" + {{ if not .spec.distribution.modules.logging.overrides.ingresses.opensearchDashboards.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: opensearch-dashboards + namespace: logging +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "logging" "package" "opensearchDashboards" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "logging" "package" "opensearchDashboards" "prefix" "opensearch-dashboards.internal." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: opensearch-dashboards + port: + name: http +{{- template "ingressTls" (dict "module" "logging" "package" "opensearchDashboards" "prefix" "opensearch-dashboards.internal." "spec" .spec) }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "MinIO Logging" + forecastle.stakater.com/icon: "https://min.io/resources/img/logo/MINIO_Bird.png" + {{ if not .spec.distribution.modules.logging.overrides.ingresses.minio.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: minio + namespace: logging +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "logging" "package" "minio" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "logging" "package" "minio" "prefix" "minio.internal." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: minio + port: + name: minio +{{- template "ingressTls" (dict "module" "logging" "package" "minio" "prefix" "minio.internal." "spec" .spec) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/monitoring/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/kustomization.yaml.tpl new file mode 100644 index 000000000..43560791d --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/kustomization.yaml.tpl @@ -0,0 +1,25 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/alertmanager-operated" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/blackbox-exporter" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/eks-sm" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/grafana" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/kube-proxy-metrics" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/kube-state-metrics" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/node-exporter" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/prometheus-adapter" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/prometheus-operated" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/prometheus-operator" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/monitoring/katalog/x509-exporter" }} # FIXME + - resources/ingress-infra.yml + {{- if or .spec.distribution.modules.monitoring.alertmanager.deadManSwitchWebhookUrl .spec.distribution.modules.monitoring.alertmanager.slackWebhookUrl }} + - secrets/alertmanager.yml + {{- end }} + +patchesStrategicMerge: + - patches/alertmanager-operated.yml + - patches/infra-nodes.yml + - patches/prometheus-operated.yml diff --git a/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/alertmanager-operated.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/alertmanager-operated.yml.tpl new file mode 100644 index 000000000..917679dd6 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/alertmanager-operated.yml.tpl @@ -0,0 +1,8 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: Alertmanager +metadata: + name: main + namespace: monitoring +spec: + externalUrl: https://{{ template "alertmanagerUrl" .spec }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..f9af0aea3 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/infra-nodes.yml.tpl @@ -0,0 +1,109 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.monitoring.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.monitoring.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{- template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) -}} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.monitoring.overrides.tolerations nil -}} + {{ .spec.distribution.modules.monitoring.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +--- +apiVersion: monitoring.coreos.com/v1 +kind: Alertmanager +metadata: + name: main + namespace: monitoring +spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec "indent" 4 ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec "indent" 4 ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: blackbox-exporter + namespace: monitoring +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grafana + namespace: monitoring +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kube-state-metrics + namespace: monitoring +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prometheus-adapter + namespace: monitoring +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: monitoring.coreos.com/v1 +kind: Prometheus +metadata: + name: k8s + namespace: monitoring +spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec "indent" 4) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec "indent" 4 ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prometheus-operator + namespace: monitoring +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/prometheus-operated.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/prometheus-operated.yml.tpl new file mode 100644 index 000000000..fb328d645 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/patches/prometheus-operated.yml.tpl @@ -0,0 +1,24 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: Prometheus +metadata: + name: k8s + namespace: monitoring +spec: + externalLabels: + k8s_cluster: {{ .metadata.name }} + externalUrl: https://{{ template "prometheusUrl" .spec }} + {{- if hasKeyAny .spec.distribution.modules.monitoring.prometheus "resources" }} + resources: + {{ .spec.distribution.modules.monitoring.prometheus.resources | toYaml | indent 4 | trim }} + {{- end }} + retention: {{ .spec.distribution.modules.monitoring.prometheus.retentionTime }} + retentionSize: {{ .spec.distribution.modules.monitoring.prometheus.retentionSize }} + storage: + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .spec.distribution.modules.monitoring.prometheus.storageSize }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/monitoring/resources/ingress-infra.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/resources/ingress-infra.yml.tpl new file mode 100644 index 000000000..810fe5aad --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/resources/ingress-infra.yml.tpl @@ -0,0 +1,78 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Alertmanager" + forecastle.stakater.com/icon: "https://github.com/stakater/ForecastleIcons/raw/master/alert-manager.png" + {{ if not .spec.distribution.modules.ingress.overrides.ingresses.forecastle.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: alertmanager + namespace: monitoring +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "monitoring" "package" "alertmanager" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "alertmanagerUrl" .spec }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: alertmanager-main + port: + name: web +{{- template "ingressTls" (dict "module" "monitoring" "package" "alertmanager" "prefix" "alertmanager.internal." "spec" .spec) }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Grafana" + forecastle.stakater.com/icon: "https://github.com/stakater/ForecastleIcons/raw/master/grafana.png" + {{ if not .spec.distribution.modules.ingress.overrides.ingresses.forecastle.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: grafana + namespace: monitoring +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "monitoring" "package" "grafana" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "monitoring" "package" "grafana" "prefix" "grafana.internal." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: grafana + port: + name: http +{{- template "ingressTls" (dict "module" "monitoring" "package" "grafana" "prefix" "grafana.internal." "spec" .spec) }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Prometheus" + forecastle.stakater.com/icon: "https://github.com/stakater/ForecastleIcons/raw/master/prometheus.png" + {{ if not .spec.distribution.modules.ingress.overrides.ingresses.forecastle.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: prometheus + namespace: monitoring +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "monitoring" "package" "prometheus" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "prometheusUrl" .spec }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: prometheus-k8s + port: + name: web +{{- template "ingressTls" (dict "module" "monitoring" "package" "prometheus" "prefix" "prometheus.internal." "spec" .spec) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/monitoring/secrets/alertmanager.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/secrets/alertmanager.yml.tpl new file mode 100644 index 000000000..9d696b64e --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/monitoring/secrets/alertmanager.yml.tpl @@ -0,0 +1,28 @@ +{{- if .spec.distribution.modules.monitoring.alertmanager.deadManSwitchWebhookUrl -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: healthchecks-webhook + namespace: monitoring +data: + url: {{ .spec.distribution.modules.monitoring.alertmanager.deadManSwitchWebhookUrl | b64enc }} +{{ end -}} +{{ if .spec.distribution.modules.monitoring.alertmanager.slackWebhookUrl -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: infra-slack-webhook + namespace: monitoring +data: + url: {{ .spec.distribution.modules.monitoring.alertmanager.slackWebhookUrl | b64enc }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: k8s-slack-webhook + namespace: monitoring +data: + url: {{ .spec.distribution.modules.monitoring.alertmanager.slackWebhookUrl | b64enc }} +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/networking/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/networking/kustomization.yaml.tpl new file mode 100644 index 000000000..2ed37c07a --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/networking/kustomization.yaml.tpl @@ -0,0 +1,7 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/networking/katalog/tigera/eks-policy-only" }} + diff --git a/test/data/expensive/common/data/templates/distribution/manifests/opa/kustomization.yaml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/opa/kustomization.yaml.tpl new file mode 100644 index 000000000..1db6fe727 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/opa/kustomization.yaml.tpl @@ -0,0 +1,23 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/opa/katalog/gatekeeper/core" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/opa/katalog/gatekeeper/gpm" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/opa/katalog/gatekeeper/rules" }} + - {{ print "../" .spec.distribution.common.relativeVendorPath "/modules/opa/katalog/gatekeeper/monitoring" }} + - resources/ingress-infra.yml + +patchesStrategicMerge: + - patches/infra-nodes.yml + +{{ if .spec.distribution.modules.policy.gatekeeper.additionalExcludedNamespaces }} +patchesJson6902: + - target: + group: config.gatekeeper.sh + version: v1alpha1 + kind: Config + name: config + path: patches/gatekeeper-whitelist-namespace.yml +{{ end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/opa/patches/gatekeeper-whitelist-namespace.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/opa/patches/gatekeeper-whitelist-namespace.yml.tpl new file mode 100644 index 000000000..057e96fba --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/opa/patches/gatekeeper-whitelist-namespace.yml.tpl @@ -0,0 +1,8 @@ +{{ if .spec.distribution.modules.policy.gatekeeper.additionalExcludedNamespaces }} +--- +{{ range .spec.distribution.modules.policy.gatekeeper.additionalExcludedNamespaces }} +- op: "add" + path: "/spec/match/0/excludedNamespaces/-" + value: {{ . }} +{{ end }} +{{ end }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/opa/patches/infra-nodes.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/opa/patches/infra-nodes.yml.tpl new file mode 100644 index 000000000..3912c033e --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/opa/patches/infra-nodes.yml.tpl @@ -0,0 +1,48 @@ +{{- define "nodeSelector" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.policy.overrides.nodeSelector nil -}} + {{ .spec.distribution.modules.policy.overrides.nodeSelector | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonNodeSelector" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +{{- define "tolerations" -}} + {{ $indent := 8 -}} + {{ if hasKey . "indent" -}} + {{ $indent = .indent -}} + {{- end -}} + {{ if ne .spec.distribution.modules.policy.overrides.tolerations nil -}} + {{ .spec.distribution.modules.policy.overrides.tolerations | toYaml | indent $indent | trim }} + {{- else -}} + {{ template "commonTolerations" ( dict "spec" .spec "indent" $indent ) }} + {{- end }} +{{- end -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gatekeeper-audit + namespace: gatekeeper-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gatekeeper-controller-manager + namespace: gatekeeper-system +spec: + template: + spec: + nodeSelector: + {{ template "nodeSelector" ( dict "spec" .spec ) }} + tolerations: + {{ template "tolerations" ( dict "spec" .spec ) }} diff --git a/test/data/expensive/common/data/templates/distribution/manifests/opa/resources/ingress-infra.yml.tpl b/test/data/expensive/common/data/templates/distribution/manifests/opa/resources/ingress-infra.yml.tpl new file mode 100644 index 000000000..14791e03a --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/manifests/opa/resources/ingress-infra.yml.tpl @@ -0,0 +1,26 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + forecastle.stakater.com/expose: "true" + forecastle.stakater.com/appName: "Gatekeeper Policy Manager" + forecastle.stakater.com/icon: "https://raw.githubusercontent.com/sighupio/gatekeeper-policy-manager/master/app/static-content/logo.svg" + {{ if not .spec.distribution.modules.policy.overrides.ingresses.gpm.disableAuth }}{{ template "ingressAuth" . }}{{ end }} + {{ template "certManagerClusterIssuer" . }} + name: gpm + namespace: gatekeeper-system +spec: + ingressClassName: {{ template "ingressClass" (dict "module" "policy" "package" "gpm" "type" "internal" "spec" .spec) }} + rules: + - host: {{ template "ingressHost" (dict "module" "policy" "package" "gpm" "prefix" "gpm.internal." "spec" .spec) }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gatekeeper-policy-manager + port: + name: http +{{- template "ingressTls" (dict "module" "policy" "package" "gpm" "prefix" "gpm.internal." "spec" .spec) }} diff --git a/test/data/expensive/common/data/templates/distribution/terraform/cert-manager.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/cert-manager.tf.tpl new file mode 100644 index 000000000..4987d0e4c --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/cert-manager.tf.tpl @@ -0,0 +1,12 @@ +{{- if eq .spec.distribution.modules.ingress.certManager.clusterIssuer.type "dns01" -}} +module "cert_manager_iam_role" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/ingress/modules/aws-cert-manager" }}" + cluster_name = "{{ .metadata.name }}" + public_zone_id = "{{ .spec.distribution.modules.ingress.certManager.clusterIssuer.route53.hostedZoneId }}" +} + +output "cert_manager_iam_role_arn" { + value = module.cert_manager_iam_role.cert_manager_iam_role_arn +} + +{{- end }} diff --git a/test/data/expensive/common/data/templates/distribution/terraform/cluster-autoscaler.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/cluster-autoscaler.tf.tpl new file mode 100644 index 000000000..86b0e36df --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/cluster-autoscaler.tf.tpl @@ -0,0 +1,9 @@ +module "cluster_autoscaler_iam_role" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/aws/modules/iam-for-cluster-autoscaler" }}" + cluster_name = "{{ .metadata.name }}" + region = "{{ .spec.toolsConfiguration.terraform.state.s3.region }}" +} + +output "cluster_autoscaler_iam_role_arn" { + value = module.cluster_autoscaler_iam_role.cluster_autoscaler_iam_role_arn +} diff --git a/test/data/expensive/common/data/templates/distribution/terraform/ebs-csi-driver.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/ebs-csi-driver.tf.tpl new file mode 100644 index 000000000..c58e20d85 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/ebs-csi-driver.tf.tpl @@ -0,0 +1,8 @@ +module "ebs_csi_driver_iam_role" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/aws/modules/iam-for-ebs-csi-driver" }}" + cluster_name = "{{ .metadata.name }}" +} + +output "ebs_csi_driver_iam_role_arn" { + value = module.ebs_csi_driver_iam_role.ebs_csi_driver_iam_role_arn +} diff --git a/test/data/expensive/common/data/templates/distribution/terraform/external-dns.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/external-dns.tf.tpl new file mode 100644 index 000000000..487a644c4 --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/external-dns.tf.tpl @@ -0,0 +1,43 @@ +{{- if (eq .spec.distribution.modules.ingress.nginx.type "single") }} +module "external_dns" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/ingress/modules/aws-external-dns" }}" +{{- if (.spec.distribution.modules.ingress.dns.public.create)}} + public_zone_id = aws_route53_zone.public.zone_id +{{- else }} + public_zone_id = data.aws_route53_zone.public.zone_id +{{- end }} + cluster_name = "{{ .metadata.name }}" +} + +output "external_dns_public_iam_role_arn" { + value = module.external_dns.external_dns_public_iam_role_arn +} + +{{- end }} + +{{- if (eq .spec.distribution.modules.ingress.nginx.type "dual") }} + +module "external_dns" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/ingress/modules/aws-external-dns" }}" +{{- if (.spec.distribution.modules.ingress.dns.public.create)}} + public_zone_id = aws_route53_zone.public.zone_id +{{- else }} + public_zone_id = data.aws_route53_zone.public.zone_id +{{- end }} +{{- if (.spec.distribution.modules.ingress.dns.private.create)}} + private_zone_id = aws_route53_zone.private.zone_id +{{- else }} + private_zone_id = data.aws_route53_zone.private.zone_id +{{- end }} + cluster_name = "{{ .metadata.name }}" +} + +output "external_dns_public_iam_role_arn" { + value = module.external_dns.external_dns_public_iam_role_arn +} + +output "external_dns_private_iam_role_arn" { + value = module.external_dns.external_dns_private_iam_role_arn +} + +{{- end }} \ No newline at end of file diff --git a/test/data/expensive/common/data/templates/distribution/terraform/load-balancer-controller.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/load-balancer-controller.tf.tpl new file mode 100644 index 000000000..023465a5a --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/load-balancer-controller.tf.tpl @@ -0,0 +1,9 @@ +module "load_balancer_controller_iam_role" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/aws/modules/iam-for-load-balancer-controller" }}" + cluster_name = "{{ .metadata.name }}" +} + +output "load_balancer_controller_iam_role_arn" { + value = module.load_balancer_controller_iam_role.load_balancer_controller_iam_role_arn +} + diff --git a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl new file mode 100644 index 000000000..7bccf0e7f --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl @@ -0,0 +1,15 @@ +terraform { + backend "s3" { + bucket = "{{ .spec.toolsConfiguration.terraform.state.s3.bucketName }}" + key = "{{ .spec.toolsConfiguration.terraform.state.s3.keyPrefix }}/distribution.json" + region = "{{ .spec.toolsConfiguration.terraform.state.s3.region }}" + } +} + +provider "aws" { + region = "{{ .spec.toolsConfiguration.terraform.state.s3.region }}" # FIXME +} + +data "aws_eks_cluster" "this" { + name = "{{ .metadata.name }}" +} diff --git a/test/data/expensive/common/data/templates/distribution/terraform/route53.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/route53.tf.tpl new file mode 100644 index 000000000..51426190b --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/route53.tf.tpl @@ -0,0 +1,59 @@ +# Create public DNS if nginx is single or dual and create is true + +{{- if (.spec.distribution.modules.ingress.dns.public.create)}} + +resource "aws_route53_zone" "public" { + name = "{{ .spec.distribution.modules.ingress.dns.public.name }}" +} + +output "aws_route53_zone_public_id" { + value = aws_route53_zone.public.zone_id +} + +output "aws_route53_zone_public_name_servers" { + value = aws_route53_zone.public.name_servers +} + +{{- end }} + +# Get public DNS as data if nginx is single or dual and create is false +{{- if (not .spec.distribution.modules.ingress.dns.public.create) }} + +data "aws_route53_zone" "public" { + name = "{{ .spec.distribution.modules.ingress.dns.public.name }}" +} + +output "aws_route53_zone_public_id" { + value = data.aws_route53_zone.public.zone_id +} + +{{- end }} + +# Create private DNS if nginx is dual and create is true +{{- if and (.spec.distribution.modules.ingress.dns.private.create) (eq .spec.distribution.modules.ingress.nginx.type "dual") }} + +resource "aws_route53_zone" "private" { + name = "{{ .spec.distribution.modules.ingress.dns.private.name }}" + vpc { + vpc_id = "{ .spec.distribution.modules.ingress.dns.private.vpcId }" + } +} + +output "aws_route53_zone_private_id" { + value = aws_route53_zone.private.zone_id +} + +{{- end }} +# Get private DNS as data if nginx is dual and create is false +{{- if and (not .spec.distribution.modules.ingress.dns.private.create) (eq .spec.distribution.modules.ingress.nginx.type "dual") }} + +data "aws_route53_zone" "private" { + name = "{{ .spec.distribution.modules.ingress.dns.private.name }}" + vpc_id = "{ .spec.distribution.modules.ingress.dns.private.vpcId }" +} + +output "aws_route53_zone_private_id" { + value = data.aws_route53_zone.private.zone_id +} + +{{- end }} \ No newline at end of file diff --git a/test/data/expensive/common/data/templates/distribution/terraform/velero.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/velero.tf.tpl new file mode 100644 index 000000000..1490ae49c --- /dev/null +++ b/test/data/expensive/common/data/templates/distribution/terraform/velero.tf.tpl @@ -0,0 +1,9 @@ +module "velero" { + source = "{{ print .spec.distribution.common.relativeVendorPath "/modules/dr/modules/aws-velero" }}" + backup_bucket_name = "{{ .spec.distribution.modules.dr.velero.eks.bucketName }}" + oidc_provider_url = replace(data.aws_eks_cluster.this.identity.0.oidc.0.issuer, "https://", "") +} + +output "velero_iam_role_arn" { + value = module.velero.velero_iam_role_arn +} diff --git a/test/data/expensive/create-complete/data/furyctl.yaml b/test/data/expensive/create-complete/data/furyctl.yaml new file mode 100644 index 000000000..2ce970c43 --- /dev/null +++ b/test/data/expensive/create-complete/data/furyctl.yaml @@ -0,0 +1,129 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: furyctl-test-aws +spec: + distributionVersion: "v1.24.1" + toolsConfiguration: + terraform: + state: + s3: + bucketName: furyctl-test-ci + keyPrefix: furyctl/ + region: eu-west-1 + region: eu-west-1 + tags: + env: "test" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.0.0.0/16 + subnetsCidrs: + private: + - 10.0.182.0/24 + - 10.0.172.0/24 + - 10.0.162.0/24 + public: + - 10.0.20.0/24 + - 10.0.30.0/24 + - 10.0.40.0/24 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePools: + - name: worker-eks + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 2 + max: 3 + subnetIds: [] + instance: + type: t3.large + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: [] + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + - name: infra + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 3 + max: 3 + subnetIds: [] + instance: + type: t3.xlarge + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: infra + node.kubernetes.io/role: infra + taints: + - node.kubernetes.io/role=infra:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "infra" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "infra" + k8s.io/cluster-autoscaler/node-template/taint/node.kubernetes.io/role: "infra:NoSchedule" + additionalFirewallRules: [] + awsAuth: + additionalAccounts: [] + users: [] + roles: [] + distribution: + modules: + ingress: + baseDomain: furyctl-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + certManager: + clusterIssuer: + name: letsencrypt-fury + type: http01 + dns: + public: + name: "furyctl-demo.sighup.io" + create: true + private: + create: true + name: "internal.furyctl-demo.sighup.io" + logging: + opensearch: + type: single + resources: + limits: + cpu: 2000m + memory: 4G + requests: + cpu: 300m + memory: 1G diff --git a/test/data/expensive/create-skip-infra/data/furyctl.yaml b/test/data/expensive/create-skip-infra/data/furyctl.yaml new file mode 100644 index 000000000..efb32b7b4 --- /dev/null +++ b/test/data/expensive/create-skip-infra/data/furyctl.yaml @@ -0,0 +1,129 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: furyctl-test-aws-si +spec: + distributionVersion: "v1.24.1" + toolsConfiguration: + terraform: + state: + s3: + bucketName: furyctl-test-ci + keyPrefix: furyctl/ + region: eu-west-1 + region: eu-west-1 + tags: + env: "test" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.0.0.0/16 + subnetsCidrs: + private: + - 10.0.182.0/24 + - 10.0.172.0/24 + - 10.0.162.0/24 + public: + - 10.0.20.0/24 + - 10.0.30.0/24 + - 10.0.40.0/24 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePools: + - name: worker-eks + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 2 + max: 3 + subnetIds: [] + instance: + type: t3.large + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: [] + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + - name: infra + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 3 + max: 3 + subnetIds: [] + instance: + type: t3.xlarge + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: infra + node.kubernetes.io/role: infra + taints: + - node.kubernetes.io/role=infra:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "infra" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "infra" + k8s.io/cluster-autoscaler/node-template/taint/node.kubernetes.io/role: "infra:NoSchedule" + additionalFirewallRules: [] + awsAuth: + additionalAccounts: [] + users: [] + roles: [] + distribution: + modules: + ingress: + baseDomain: furyctl-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + certManager: + clusterIssuer: + name: letsencrypt-fury + type: http01 + dns: + public: + name: "furyctl-demo.sighup.io" + create: true + private: + create: true + name: "internal.furyctl-demo.sighup.io" + logging: + opensearch: + type: single + resources: + limits: + cpu: 2000m + memory: 4G + requests: + cpu: 300m + memory: 1G diff --git a/test/data/expensive/create-skip-kube/data/furyctl.yaml b/test/data/expensive/create-skip-kube/data/furyctl.yaml new file mode 100644 index 000000000..476b3c01c --- /dev/null +++ b/test/data/expensive/create-skip-kube/data/furyctl.yaml @@ -0,0 +1,129 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: furyctl-test-aws-sk +spec: + distributionVersion: "v1.24.1" + toolsConfiguration: + terraform: + state: + s3: + bucketName: furyctl-test-ci + keyPrefix: furyctl/ + region: eu-west-1 + region: eu-west-1 + tags: + env: "test" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.0.0.0/16 + subnetsCidrs: + private: + - 10.0.182.0/24 + - 10.0.172.0/24 + - 10.0.162.0/24 + public: + - 10.0.20.0/24 + - 10.0.30.0/24 + - 10.0.40.0/24 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePools: + - name: worker-eks + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 2 + max: 3 + subnetIds: [] + instance: + type: t3.large + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: [] + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + - name: infra + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 3 + max: 3 + subnetIds: [] + instance: + type: t3.xlarge + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: infra + node.kubernetes.io/role: infra + taints: + - node.kubernetes.io/role=infra:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "infra" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "infra" + k8s.io/cluster-autoscaler/node-template/taint/node.kubernetes.io/role: "infra:NoSchedule" + additionalFirewallRules: [] + awsAuth: + additionalAccounts: [] + users: [] + roles: [] + distribution: + modules: + ingress: + baseDomain: furyctl-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + certManager: + clusterIssuer: + name: letsencrypt-fury + type: http01 + dns: + public: + name: "furyctl-demo.sighup.io" + create: true + private: + create: true + name: "internal.furyctl-demo.sighup.io" + logging: + opensearch: + type: single + resources: + limits: + cpu: 2000m + memory: 4G + requests: + cpu: 300m + memory: 1G diff --git a/test/data/integration/schema/santhosh/test-cluster-broken.json b/test/data/integration/schema/santhosh/test-cluster-broken.json new file mode 100644 index 000000000..a30d66ee2 --- /dev/null +++ b/test/data/integration/schema/santhosh/test-cluster-broken.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/test-cluster.json", + "description": "A Fury Cluster", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["TestCluster"] + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "spec": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ] diff --git a/test/data/integration/schema/santhosh/test-cluster-correct.json b/test/data/integration/schema/santhosh/test-cluster-correct.json new file mode 100644 index 000000000..1ff3214c4 --- /dev/null +++ b/test/data/integration/schema/santhosh/test-cluster-correct.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/test-cluster.json", + "description": "A Fury Cluster", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["TestCluster"] + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "spec": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ] +} diff --git a/test/data/integration/schema/santhosh/test-cluster-wrong.json b/test/data/integration/schema/santhosh/test-cluster-wrong.json new file mode 100644 index 000000000..44e31665f --- /dev/null +++ b/test/data/integration/schema/santhosh/test-cluster-wrong.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/test-cluster.json", + "description": "A Fury Cluster", + "type": "object", + "properties": { + "apiVersion": { + "type": "not-existing", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion" + ] +} diff --git a/test/data/integration/v1.24.1/distro/furyctl-defaults.yaml b/test/data/integration/v1.24.1/distro/furyctl-defaults.yaml new file mode 100644 index 000000000..707d13c4d --- /dev/null +++ b/test/data/integration/v1.24.1/distro/furyctl-defaults.yaml @@ -0,0 +1,223 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: null + tolerations: null + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: example.dev + externalDns: + privateIamRoleArn: arn:aws:iam::123456789012:role/external-dns-private + publicIamRoleArn: arn:aws:iam::123456789012:role/external-dns-public + dns: + public: + name: "" + # if create is false, a data source will be used to get the public DNS, otherwise a public zone will be created + create: false + # private is used only when ingress.nginx.type is "dual" + private: + # required to be set by the user, ex: internal.fury-demo.sighup.io + name: "" + create: true + # internal field, should be either the VPC ID taken from the kubernetes + # phase or the ID of the created VPC in the Ifra phase + vpcId: "" + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + email: engineering+fury-distribution@sighup.io + # can be dns01 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789012:role/example-route53 + region: eu-west-1 + hostedZoneId: "" + # logging module configuration + logging: + overrides: + nodeSelector: null + tolerations: null + ingresses: + opensearchDashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + minio: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storageSize: 150Gi + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: null + tolerations: null + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + retentionTime: 30d + retentionSize: 120GB + storageSize: 150Gi + alertmanager: + deadManSwitchWebhookUrl: "" + slackWebhookUrl: "" + # policy module configuration + policy: + overrides: + nodeSelector: null + tolerations: null + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: null + tolerations: null + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789012:role/example-velero + region: eu-west-1 + bucketName: example-velero + # auth module configuration + auth: + overrides: + nodeSelector: null + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: null + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + policy: "" + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: "" + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: "" + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: "" + dex: + # see dex documentation for more information + connectors: [] + aws: + overrides: + nodeSelector: null + tolerations: null + ebsCsiDriver: + iamRoleArn: arn:aws:iam::123456789012:role/ebs-csi-controller-role + loadBalancerController: + iamRoleArn: arn:aws:iam::123456789012:role/example-load-balancer-controller + clusterAutoscaler: + region: eu-west-1 + iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/test/data/integration/v1.24.1/distro/kfd.yaml b/test/data/integration/v1.24.1/distro/kfd.yaml new file mode 100644 index 000000000..3f88aae50 --- /dev/null +++ b/test/data/integration/v1.24.1/distro/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: 2.8.12 diff --git a/test/data/integration/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json b/test/data/integration/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json new file mode 100644 index 000000000..37842f285 --- /dev/null +++ b/test/data/integration/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json @@ -0,0 +1,1569 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" + }, + "kind": { + "type": "string", + "enum": ["EKSCluster"] + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "$defs": { + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 19 + } + }, + "required": [ + "name" + ] + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "distributionVersion": { + "$ref": "#/$defs/Types.SemVer" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "toolsConfiguration": { + "$ref": "#/$defs/Spec.ToolsConfiguration" + }, + "infrastructure": { + "$ref": "#/$defs/Spec.Infrastructure" + }, + "kubernetes": { + "$ref": "#/$defs/Spec.Kubernetes" + }, + "distribution": { + "$ref": "#/$defs/Spec.Distribution" + } + }, + "required": [ + "distributionVersion", + "region", + "kubernetes", + "distribution", + "toolsConfiguration" + ], + "if": { + "anyOf": [ + { + "properties": { + "infrastructure": { + "type": "null" + } + } + }, + { + "properties": { + "infrastructure": { + "properties": { + "vpc": { + "type": "null" + } + } + } + } + }, + { + "properties": { + "infrastructure": { + "properties": { + "vpc": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "kubernetes": { + "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + } + } + }, + "else": { + "properties": { + "kubernetes": { + "type": "object", + "properties": { + "vpcId": { + "type": "null" + }, + "subnetIds": { + "type": "null" + }, + "apiServerEndpointAccess": { + "type": "null" + } + } + } + } + } + }, + + "Spec.ToolsConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" + } + }, + "required": [ + "terraform" + ] + }, + "Spec.ToolsConfiguration.Terraform": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" + } + }, + "required": [ + "state" + ] + }, + "Spec.ToolsConfiguration.Terraform.State": { + "type": "object", + "additionalProperties": false, + "properties": { + "s3": { + "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" + } + }, + "required": [ + "s3" + ] + }, + "Spec.ToolsConfiguration.Terraform.State.S3": { + "type": "object", + "additionalProperties": false, + "properties": { + "bucketName": { + "type": "string" + }, + "keyPrefix": { + "type": "string", + "maxLength": 37 + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "bucketName", + "keyPrefix", + "region" + ] + }, + + "Spec.Infrastructure": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpc": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc" + } + } + }, + "Spec.Infrastructure.Vpc": { + "type": "object", + "additionalProperties": false, + "properties": { + "network": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" + } + }, + "required": [ + "network" + ] + }, + "Spec.Infrastructure.Vpc.Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "subnetsCidrs": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" + } + }, + "required": [ + "cidr", + "subnetsCidrs" + ] + }, + "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { + "type": "object", + "additionalProperties": false, + "properties": { + "private": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "public": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "private", + "public" + ] + }, + "Spec.Infrastructure.Vpc.Vpn": { + "type": "object", + "additionalProperties": false, + "properties": { + "instances": { + "type": "integer" + }, + "port": { + "$ref": "#/$defs/Types.TcpPort" + }, + "instanceType": { + "type": "string" + }, + "diskSize": { + "type": "integer" + }, + "operatorName": { + "type": "string" + }, + "dhParamsBits": { + "type": "integer" + }, + "vpnClientsSubnetCidr": { + "$ref": "#/$defs/Types.Cidr" + }, + "ssh": { + "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + } + }, + "required": [ + "ssh", + "vpnClientsSubnetCidr" + ] + }, + "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "type": "object", + "additionalProperties": false, + "properties": { + "publicKeys": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + } + }, + "githubUsersName": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedFromCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + } + }, + "required": [ + "allowedFromCidrs", + "githubUsersName", + "publicKeys" + ] + }, + + "Spec.Kubernetes": { + "type": "object", + "additionalProperties": false, + "properties": { + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "apiServerEndpointAccess": { + "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + }, + "nodeAllowedSshPublicKey": { + "anyOf": [ + { + "$ref": "#/$defs/Types.SshPubKey" + }, + { + "$ref": "#/$defs/Types.FileRef" + } + ] + }, + "nodePools": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool" + } + }, + "awsAuth": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" + } + }, + "required": [ + "nodeAllowedSshPublicKey", + "nodePools" + ] + }, + "Spec.Kubernetes.APIServerEndpointAccess": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "public", + "private", + "public_and_private" + ] + }, + "allowedCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 1 + } + }, + "required": [ + "allowedCidrs", + "type" + ] + }, + "Spec.Kubernetes.NodePool": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "ami": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" + }, + "size": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" + }, + "instance": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" + }, + "attachedTargetGroups": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "taints": { + "$ref": "#/$defs/Types.KubeTaints" + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "subnetIds": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.AwsSubnetId" + } + }, + "additionalFirewallRules": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" + } + } + }, + "required": [ + "ami", + "instance", + "name", + "size" + ] + }, + "Spec.Kubernetes.NodePool.Ami": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "owner" + ] + }, + "Spec.Kubernetes.NodePool.Instance": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "spot": { + "type": "boolean" + }, + "volumeSize": { + "type": "integer" + } + }, + "required": [ + "type" + ] + }, + "Spec.Kubernetes.NodePool.Size": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer", + "minimum": 0 + }, + "max": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "max", + "min" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + } + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/$defs/Types.TcpPort" + }, + "to": { + "$ref": "#/$defs/Types.TcpPort" + } + }, + "required": [ + "from", + "to" + ] + }, + "Spec.Kubernetes.AwsAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" + } + } + } + }, + "Spec.Kubernetes.AwsAuth.Role": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "rolearn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "rolearn", + "username" + ] + }, + "Spec.Kubernetes.AwsAuth.User": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "userarn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "groups", + "userarn", + "username" + ] + }, + + "Spec.Distribution": { + "type": "object", + "additionalProperties": false, + "properties": { + "common": { + "$ref": "#/$defs/Spec.Distribution.Common" + }, + "modules": { + "$ref": "#/$defs/Spec.Distribution.Modules" + } + }, + "required": [ + "modules" + ], + "if": { + "allOf": [ + { + "required": ["common"] + }, + { + "properties": { + "common": { + "required": ["provider"] + } + } + }, + { + "properties": { + "common": { + "properties": { + "provider": { + "required": ["type"] + } + } + } + } + }, + { + "properties": { + "common": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "eks" + } + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "modules": { + "required": ["aws"] + } + } + }, + "else": { + "properties": { + "modules": { + "properties": { + "aws": { + "type": "null" + } + } + } + } + } + }, + "Spec.Distribution.Common": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Common.Provider" + }, + "relativeVendorPath": { + "type": "string" + } + } + }, + "Spec.Distribution.Common.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules": { + "type": "object", + "additionalProperties": false, + "properties": { + "auth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth" + }, + "aws": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws" + }, + "dr": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr" + }, + "ingress": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" + }, + "logging": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging" + }, + "monitoring": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" + }, + "policy": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy" + } + }, + "required": [ + "dr", + "ingress", + "logging" + ] + }, + "Spec.Distribution.Modules.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "baseDomain": { + "type": "string" + }, + "nginx": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" + }, + "certManager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager" + }, + "dns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" + }, + "externalDns": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + } + }, + "required": [ + "baseDomain", + "dns", + "nginx" + ] + }, + "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { + "type": "object", + "additionalProperties": false, + "properties": { + "forecastle": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + }, + "Spec.Distribution.Modules.Ingress.Nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "dual"] + }, + "tls": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "enum": ["certManager", "secret", "none"] + }, + "secret": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" + } + }, + "required": [ + "provider" + ], + "if": { + "properties": { + "provider": { + "const": "secret" + } + } + }, + "then": { + "required": ["secret"] + } + }, + "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ca": { + "type": "string" + } + }, + "required": [ + "ca", + "cert", + "key" + ] + }, + "Spec.Distribution.Modules.Ingress.CertManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "clusterIssuer": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + } + }, + "required": [ + "clusterIssuer" + ] + }, + "Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "type": { + "type": "string", + "enum": ["dns01", "http01"] + }, + "route53": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" + } + }, + "required": [ + "name", + "type", + "email" + ] + }, + "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "hostedZoneId": { + "type": "string" + } + }, + "required": [ + "hostedZoneId", + "iamRoleArn", + "region" + ] + }, + "Spec.Distribution.Modules.Ingress.ExternalDNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "privateIamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "publicIamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "privateIamRoleArn", + "publicIamRoleArn" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS": { + "type": "object", + "additionalProperties": false, + "properties": { + "public": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" + }, + "private": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + } + }, + "required": [ + "public", + "private" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Public": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + } + }, + "required": [ + "name", + "create" + ] + }, + "Spec.Distribution.Modules.Ingress.DNS.Private": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "create": { + "type": "boolean" + }, + "vpcId": { + "type": "string" + } + }, + "required": [ + "name", + "create", + "vpcId" + ] + }, + "Spec.Distribution.Modules.Logging": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "opensearch": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + } + }, + "required": ["opensearch"] + }, + "Spec.Distribution.Modules.Logging.Opensearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["single", "triple"] + }, + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "storageSize": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Monitoring": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "prometheus": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" + }, + "alertmanager": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + } + } + }, + "Spec.Distribution.Modules.Monitoring.Prometheus": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + }, + "retentionTime": { + "type": "string" + }, + "retentionSize": { + "type": "string" + }, + "storageSize": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Monitoring.AlertManager": { + "type": "object", + "additionalProperties": false, + "properties": { + "deadManSwitchWebhookUrl": { + "type": "string" + }, + "slackWebhookUrl": { + "type": "string" + } + } + }, + "Spec.Distribution.Modules.Policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "gatekeeper": { + "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" + } + } + }, + "Spec.Distribution.Modules.Policy.Gatekeeper": { + "type": "object", + "additionalProperties": false, + "properties": { + "additionalExcludedNamespaces": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Spec.Distribution.Modules.Dr": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "velero": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" + } + }, + "required": ["velero"] + }, + "Spec.Distribution.Modules.Dr.Velero": { + "type": "object", + "additionalProperties": false, + "properties": { + "eks": { + "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + } + }, + "required": ["eks"] + }, + "Spec.Distribution.Modules.Dr.Velero.Eks": { + "type": "object", + "additionalProperties": false, + "properties": { + "region": { + "$ref": "#/$defs/Types.AwsRegion" + }, + "bucketName": { + "type": "string" + }, + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "region", + "bucketName", + "iamRoleArn" + ] + }, + "Spec.Distribution.Modules.Auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" + }, + "provider": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" + }, + "pomerium": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" + }, + "dex": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" + } + }, + "required": ["provider"], + "allOf": [ + { + "if": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "sso" + } + } + } + } + }, + "then": { + "properties": { + "auth": { + "required": ["dex", "pomerium"] + } + } + }, + "else": { + "properties": { + "dex": { + "type": "null" + }, + "pomerium": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "provider": { + "properties": { + "type": { + "const": "basicAuth" + } + } + } + } + }, + "then": { + "properties": { + "provider": { + "required": ["basicAuth"] + } + } + }, + "else": { + "properties": { + "provider": { + "basicAuth": { + "type": "null" + } + } + } + } + } + ] + }, + "Spec.Distribution.Modules.Auth.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingress" + } + } + } + }, + "Spec.Distribution.Modules.Auth.Overrides.Ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "host", + "ingressClass" + ] + }, + "Spec.Distribution.Modules.Auth.Provider": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["none", "basicAuth", "sso"] + }, + "basicAuth": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" + } + }, + "required": [ + "type" + ] + }, + "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" + }, + "policy": { + "type": "string" + } + }, + "required": [ + "secrets", + "policy" + ] + }, + "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "COOKIE_SECRET": { + "type": "string" + }, + "IDP_CLIENT_SECRET": { + "type": "string" + }, + "SHARED_SECRET": { + "type": "string" + } + }, + "required": [ + "COOKIE_SECRET", + "IDP_CLIENT_SECRET", + "SHARED_SECRET" + ] + }, + "Spec.Distribution.Modules.Auth.Dex": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectors": { + "type": "array" + } + }, + "required": ["connectors"] + }, + "Spec.Distribution.Modules.Aws": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" + }, + "clusterAutoscaler": { + "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + }, + "ebsCsiDriver": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn" + ] + }, + "loadBalancerController": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + } + }, + "required": [ + "iamRoleArn" + ] + } + } + }, + + "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { + "type": "object", + "additionalProperties": false, + "properties": { + "iamRoleArn": { + "$ref": "#/$defs/Types.AwsArn" + }, + "region": { + "$ref": "#/$defs/Types.AwsRegion" + } + }, + "required": [ + "iamRoleArn", + "region" + ] + }, + + "Types.SemVer": { + "type": "string", + "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "Types.IpAddress": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" + }, + "Types.Cidr": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" + }, + "Types.FileRef": { + "type": "string", + "pattern": "^\\{file\\:\\/\\/.+\\}$" + }, + "Types.EnvRef": { + "type": "string", + "pattern": "\\{^env\\:\\/\\/.*\\}$" + }, + "Types.TcpPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "Types.SshPubKey": { + "type": "string", + "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" + }, + "Types.Uri": { + "type": "string", + "pattern": "^(http|https)\\:\\/\\/.+$" + }, + "Types.AwsArn": { + "type": "string", + "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{17}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", + "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + } + }, + "Types.FuryModuleOverridesIngress": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + }, + "required": [ + "disableAuth", + "host", + "ingressClass" + ] + } + } +} diff --git a/test/data/integration/v1.24.1/furyctl.yaml b/test/data/integration/v1.24.1/furyctl.yaml new file mode 100644 index 000000000..15b304920 --- /dev/null +++ b/test/data/integration/v1.24.1/furyctl.yaml @@ -0,0 +1,244 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: awesome-cluster-staging +spec: + distributionVersion: v1.24.1 + toolsConfiguration: + terraform: + state: + s3: + bucketName: awesome-bucket-created-outside-furyctl + keyPrefix: furyctl/ + region: eu-west-1 + tags: + env: "staging" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.1.0.0/16 + subnetsCidrs: + private: + - 10.1.0.0/20 + - 10.1.16.0/20 + - 10.1.32.0/20 + public: + - 10.1.48.0/24 + - 10.1.49.0/24 + - 10.1.50.0/24 + vpn: + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 172.16.0.0/16 + ssh: + publicKeys: + - "ssh-ed25519 XYX" + - "{file://relative/path/to/ssh.pub}" + githubUsersName: + - lnovara + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + vpcId: vpc-0f92da9b4a2089963 + subnetIds: + - subnet-0ab84702287e38ccb + - subnet-0ae4e9199d9192226 + - subnet-01787e8da51e4f070 + apiServerEndpointAccess: + type: private + allowedCidrs: + - 10.0.0.0/16 + nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePools: + - name: worker + ami: + id: null + owner: null + size: + min: 1 + max: 3 + instance: + type: t3.micro + spot: false + volumeSize: 50 + attachedTargetGroups: + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-external-nginx/6294703cfe87d756 + - arn:aws:elasticloadbalancing:eu-west-1:026908869948:targetgroup/adv-staging-internal-nginx/81625fbe0462e332 + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: + - node.kubernetes.io/role=worker:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 + awsAuth: + additionalAccounts: + - "777777777777" + - "88888888888" + users: + - username: "samuele" + groups: + - system:masters + userarn: "arn:aws:iam::363601582189:user/samuele" + roles: + - username: "sighup-support" + groups: + - sighup-support:masters + rolearn: "arn:aws:iam::363601582189:role/k8s-sighup-support-role" + distribution: + common: + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + modules: + ingress: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + forecastle: + disableAuth: false + host: "" + ingressClass: "" + baseDomain: internal.fury-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + secret: + cert: "{file://relative/path/to/ssl.crt}" + key: "{file://relative/path/to/ssl.key}" + ca: "{file://relative/path/to/ssl.ca}" + certManager: + clusterIssuer: + name: letsencrypt-fury + type: http01 + dns: + public: + name: "fury-demo.sighup.io" + create: false + private: + name: "internal.fury-demo.sighup.io" + vpcId: "vpc123123123123" + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + type: single + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + storageSize: "150Gi" + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + policy: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + gatekeeper: + additionalExcludedNamespaces: [] + dr: + overrides: + nodeSelector: {} + tolerations: [] + auth: + overrides: + nodeSelector: {} + ingresses: + pomerium: + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: [] + provider: + type: none + basicAuth: + username: admin + password: "{env://KFD_BASIC_AUTH_PASSWORD}" + pomerium: + secrets: + COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" + IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" + SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" + dex: + connectors: + - type: github + id: github + name: GitHub + config: + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" + redirectURI: https://login.fury-demo.sighup.io/callback + loadAllGroups: false + teamNameField: slug + useLoginAsID: false diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go new file mode 100644 index 000000000..9743a8a2d --- /dev/null +++ b/test/e2e/furyctl_test.go @@ -0,0 +1,618 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build e2e + +package e2e_test + +import ( + "bytes" + "fmt" + "math/rand" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" + + "github.com/sighupio/furyctl/internal/cluster" +) + +func TestE2e(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Furyctl E2e Suite") +} + +var ( + furyctl string + + Abs = func(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + Fail(err.Error()) + } + + return absPath + } + + FileContent = func(path string) string { + content, ferr := os.ReadFile(path) + if ferr != nil { + Fail(ferr.Error()) + } + + return string(content) + } + + FindFileStartingWith = func(pt, prefix string) (string, error) { + files, err := os.ReadDir(pt) + if err != nil { + return "", err + } + + for _, f := range files { + if f.IsDir() { + continue + } + + if strings.HasPrefix(f.Name(), prefix) { + return path.Join(pt, f.Name()), nil + } + } + + return "", fmt.Errorf("file not found in dir %s starting with name %s", pt, prefix) + } + + MkdirTemp = func(pattern string) string { + tmpdir, err := os.MkdirTemp("", pattern) + if err != nil { + Fail(err.Error()) + } + + return tmpdir + } + + RemoveAll = func(path string) { + if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) { + Fail(err.Error()) + } + } + + BackupEnvVars = func(vars ...string) func() { + backup := make(map[string]string) + remove := make([]string, 0) + + for _, v := range vars { + if val, ok := os.LookupEnv(v); ok { + backup[v] = val + } else { + remove = append(remove, v) + } + } + + return func() { + for k, v := range backup { + os.Setenv(k, v) + } + + for _, v := range remove { + os.Unsetenv(v) + } + } + } + + RunCmd = func(cmd string, args ...string) (string, error) { + out, err := exec.Command(cmd, args...).CombinedOutput() + + return string(out), err + } + + _ = BeforeSuite(func() { + tmpdir := MkdirTemp("furyctl-e2e") + + furyctl = filepath.Join(tmpdir, "furyctl") + + if out, err := RunCmd("go", "build", "-o", furyctl, "../../main.go"); err != nil { + Fail(fmt.Sprintf("Could not build furyctl: %v\nOutput: %s", err, out)) + } + }) + + _ = Describe("furyctl", func() { + Context("version display", func() { + It("should print its version information", func() { + out, err := RunCmd(furyctl, "version", "--disable-analytics", "--log", "stdout") + + Expect(err).To(Not(HaveOccurred())) + Expect(out).To(ContainSubstring( + "buildTime: unknown\n" + + "gitCommit: unknown\n" + + "goVersion: unknown\n" + + "osArch: unknown\n" + + "version: unknown\n", + )) + }) + }) + + Context("config validation", func() { + FuryctlValidateConfig := func(basepath string) (string, error) { + absBasepath := Abs(basepath) + + return RunCmd( + furyctl, "validate", "config", + "--config", filepath.Join(absBasepath, "furyctl.yaml"), + "--distro-location", absBasepath, + "--debug", + "--disable-analytics", "true", + ) + } + + It("should report an error when the furyctl.yaml is not found", func() { + out, err := FuryctlValidateConfig("../data/e2e/validate/config/") + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("furyctl.yaml: no such file or directory")) + }) + + It("should report an error when the kfd.yaml is not found", func() { + out, err := FuryctlValidateConfig("../data/e2e/validate/config/nodistro") + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("kfd.yaml: no such file or directory")) + }) + + It("should report an error when config validation fails", func() { + out, err := FuryctlValidateConfig("../data/e2e/validate/config/wrong") + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("config validation failed")) + }) + + It("should exit without errors when config validation succeeds", func() { + out, err := FuryctlValidateConfig("../data/e2e/validate/config/correct") + + Expect(err).To(Not(HaveOccurred())) + Expect(out).To(ContainSubstring("config validation succeeded")) + }) + }) + + Context("dependencies validation", func() { + FuryctlValidateDependencies := func(basepath, binpath string) (string, error) { + absBasepath := Abs(basepath) + + return RunCmd( + furyctl, "validate", "dependencies", + "--config", filepath.Join(absBasepath, "furyctl.yaml"), + "--distro-location", absBasepath, + "--bin-path", binpath, + "--debug", + "--disable-analytics", "true", + "--log", "stdout", + ) + } + + It("should report an error when dependencies are missing", func() { + RestoreEnvVars := BackupEnvVars("PATH", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_DEFAULT_REGION") + defer RestoreEnvVars() + + os.Unsetenv("AWS_ACCESS_KEY_ID") + os.Unsetenv("AWS_SECRET_ACCESS_KEY") + os.Unsetenv("AWS_DEFAULT_REGION") + + out, err := FuryctlValidateDependencies("../data/e2e/validate/dependencies/missing", "/tmp") + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("terraform:")) + Expect(out).To(ContainSubstring("kubectl:")) + Expect(out).To(ContainSubstring("kustomize:")) + Expect(out).To(ContainSubstring("furyagent:")) + Expect(out).To(ContainSubstring("AWS_ACCESS_KEY_ID:")) + Expect(out).To(ContainSubstring("AWS_SECRET_ACCESS_KEY:")) + Expect(out).To(ContainSubstring("AWS_DEFAULT_REGION:")) + }) + + It("should report an error when dependencies are wrong", Serial, func() { + RestoreEnvVars := BackupEnvVars( + "PATH", + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_DEFAULT_REGION", + "FURYCTL_MIXPANEL_TOKEN", + ) + defer RestoreEnvVars() + + bp := Abs("../data/e2e/validate/dependencies/wrong") + + os.Setenv("PATH", bp+":"+os.Getenv("PATH")) + os.Unsetenv("AWS_ACCESS_KEY_ID") + os.Unsetenv("AWS_SECRET_ACCESS_KEY") + os.Unsetenv("AWS_DEFAULT_REGION") + os.Unsetenv("FURYCTL_MIXPANEL_TOKEN") + + out, err := FuryctlValidateDependencies(bp, bp) + + Expect(err).To(HaveOccurred()) + Expect(out).To( + ContainSubstring("furyagent: wrong tool version - installed = 0.2.4, expected = 0.3.0"), + ) + Expect(out).To( + ContainSubstring("kubectl: wrong tool version - installed = 1.24.7, expected = 1.24.9"), + ) + Expect(out).To( + ContainSubstring("kustomize: wrong tool version - installed = 3.5.0, expected = 3.5.3"), + ) + Expect(out).To( + ContainSubstring("terraform: wrong tool version - installed = 0.15.3, expected = 0.15.4"), + ) + Expect(out).To(ContainSubstring("AWS_ACCESS_KEY_ID: missing environment variable")) + Expect(out).To(ContainSubstring("AWS_SECRET_ACCESS_KEY: missing environment variable")) + Expect(out).To(ContainSubstring("AWS_DEFAULT_REGION: missing environment variable")) + }) + + It("should exit without errors when dependencies are correct", Serial, func() { + RestoreEnvVars := BackupEnvVars( + "PATH", + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_DEFAULT_REGION", + "FURYCTL_MIXPANEL_TOKEN", + ) + defer RestoreEnvVars() + + bp := Abs("../data/e2e/validate/dependencies/correct") + + os.Setenv("PATH", bp+":"+os.Getenv("PATH")) + os.Setenv("AWS_ACCESS_KEY_ID", "test") + os.Setenv("AWS_SECRET_ACCESS_KEY", "test") + os.Setenv("AWS_DEFAULT_REGION", "test") + os.Setenv("FURYCTL_MIXPANEL_TOKEN", "test") + + out, err := FuryctlValidateDependencies(bp, bp) + + Expect(err).To(Not(HaveOccurred())) + Expect(out).To(ContainSubstring("Dependencies validation succeeded")) + }) + }) + + Context("dependencies download", Label("slow"), func() { + basepath := "../data/e2e/download/dependencies" + FuryctlDownloadDependencies := func(basepath string) (string, error) { + absBasepath := Abs(basepath) + + return RunCmd( + furyctl, "download", "dependencies", + "--config", filepath.Join(absBasepath, "furyctl.yaml"), + "--distro-location", absBasepath+"/distro", + "--workdir", absBasepath, + "--debug", + "--disable-analytics", + "--log", "stdout", + ) + } + + It("should download all dependencies for v1.24.1", func() { + bp := basepath + "/v1.24.1" + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + vp := path.Join(homeDir, ".furyctl", "awesome-cluster-staging", "vendor") + binP := path.Join(homeDir, ".furyctl", "bin") + + RemoveAll(vp) + defer RemoveAll(vp) + + _, err = FuryctlDownloadDependencies(bp) + + Expect(err).To(Not(HaveOccurred())) + Expect(binP + "/furyagent/0.3.0/furyagent").To(BeAnExistingFile()) + Expect(binP + "/kubectl/1.24.9/kubectl").To(BeAnExistingFile()) + Expect(binP + "/kustomize/3.5.3/kustomize").To(BeAnExistingFile()) + Expect(binP + "/terraform/0.15.4/terraform").To(BeAnExistingFile()) + Expect(vp + "/installers/eks/README.md").To(BeAnExistingFile()) + Expect(vp + "/installers/eks/modules/eks/main.tf").To(BeAnExistingFile()) + Expect(vp + "/installers/eks/modules/vpc-and-vpn/main.tf").To(BeAnExistingFile()) + Expect(vp + "/modules/auth/README.md").To(BeAnExistingFile()) + Expect(vp + "/modules/auth/katalog/gangway/kustomization.yaml").To(BeAnExistingFile()) + Expect(vp + "/modules/dr/README.md").To(BeAnExistingFile()) + Expect(vp + "/modules/dr/katalog/velero/velero-aws/kustomization.yaml").To(BeAnExistingFile()) + Expect(vp + "/modules/ingress/README.md").To(BeAnExistingFile()) + Expect(vp + "/modules/ingress/katalog/nginx/kustomization.yaml").To(BeAnExistingFile()) + Expect(vp + "/modules/logging/README.md").To(BeAnExistingFile()) + Expect(vp + "/modules/logging/katalog/configs/kustomization.yaml").To(BeAnExistingFile()) + Expect(vp + "/modules/monitoring/README.md").To(BeAnExistingFile()) + Expect(vp + "/modules/monitoring/katalog/configs/kustomization.yaml").To(BeAnExistingFile()) + Expect(vp + "/modules/opa/README.md").To(BeAnExistingFile()) + Expect(vp + "/modules/opa/katalog/gatekeeper/kustomization.yaml").To(BeAnExistingFile()) + }) + }) + + Context("template dump", func() { + basepath := "../data/e2e/dump/template" + FuryctlDumpTemplate := func(workdir string, dryRun bool) (string, error) { + args := []string{ + "dump", "template", + "--debug", + "--workdir", workdir, + "--disable-analytics", + "--log", "stdout", + } + if dryRun { + args = append(args, "--dry-run") + } + + return RunCmd(furyctl, args...) + } + Setup := func(folder string) string { + bp := filepath.Join(basepath, folder) + tp := filepath.Join(bp, "target") + + RemoveAll(tp) + + return bp + } + + It("fails if no distribution yaml is found", func() { + bp := Setup("no-distribution-yaml") + + out, err := FuryctlDumpTemplate(bp, false) + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("furyctl-defaults.yaml: no such file or directory")) + }) + + It("fails if no furyctl.yaml file is found", func() { + bp := Setup("no-furyctl-yaml") + + out, err := FuryctlDumpTemplate(bp, false) + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("furyctl.yaml: no such file or directory")) + }) + + It("fails if no data properties are found in furyctl-defaults.yaml file", func() { + bp := Setup("distribution-yaml-no-data-property") + + out, err := FuryctlDumpTemplate(bp, false) + + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring("incorrect base file, cannot access key data on map")) + }) + + It("fails if given an empty template", func() { + bp := Setup("empty") + + _, err := FuryctlDumpTemplate(bp, false) + + Expect(err).To(HaveOccurred()) + Expect(bp + "/target/file.txt").To(Not(BeAnExistingFile())) + }) + + It("succeeds when given a simple template on dry-run", func() { + bp := Setup("simple-dry-run") + + _, err := FuryctlDumpTemplate(bp, true) + + Expect(err).To(Not(HaveOccurred())) + Expect(FileContent(bp + "/target/file.txt")).To(ContainSubstring("testValue")) + }) + + It("succeeds when given a simple template", func() { + bp := Setup("simple") + + _, err := FuryctlDumpTemplate(bp, false) + + Expect(err).To(Not(HaveOccurred())) + Expect(FileContent(bp + "/target/file.txt")).To(ContainSubstring("testValue")) + }) + + It("succeeds when given a complex template on dry-run", func() { + bp := Setup("complex-dry-run") + + _, err := FuryctlDumpTemplate(bp, true) + + Expect(err).To(Not(HaveOccurred())) + Expect(bp + "/target/config/example.yaml").To(BeAnExistingFile()) + Expect(bp + "/target/kustomization.yaml").To(BeAnExistingFile()) + Expect(FileContent(bp + "/target/config/example.yaml")).To(ContainSubstring("configdata: example")) + Expect(FileContent(bp + "/target/kustomization.yaml")). + To(Equal(FileContent(bp + "/data/expected-kustomization.yaml"))) + }) + + It("succeeds when given a complex template", func() { + bp := Setup("complex") + + _, err := FuryctlDumpTemplate(bp, false) + + Expect(err).To(Not(HaveOccurred())) + Expect(bp + "/target/config/example.yaml").To(BeAnExistingFile()) + Expect(bp + "/target/kustomization.yaml").To(BeAnExistingFile()) + Expect(FileContent(bp + "/target/config/example.yaml")).To(ContainSubstring("configdata: example")) + Expect(FileContent(bp + "/target/kustomization.yaml")). + To(Equal(FileContent(bp + "/data/expected-kustomization.yaml"))) + }) + }) + + Context("create config", func() { + basepath := "../data/e2e/create/config" + FuryctlCreateConfig := func(workdir string) (string, error) { + absBasepath := Abs(basepath) + + return RunCmd( + furyctl, "create", "config", + "--config", workdir+"/target/furyctl.yaml", + "--debug", + "--disable-analytics", "true", + "--distro-location", absBasepath+"/distro", + "--version", "1.24.1", + "--log", "stdout", + ) + } + Setup := func(folder string) string { + bp := filepath.Join(basepath, folder) + tp := filepath.Join(bp, "target") + + RemoveAll(tp) + + return bp + } + + It("scaffolds a new furyctl.yaml file", func() { + bp := Setup("default") + + out, err := FuryctlCreateConfig(bp) + + fmt.Println(out) + + Expect(err).To(Not(HaveOccurred())) + Expect(bp + "/target/furyctl.yaml").To(BeAnExistingFile()) + Expect(FileContent(bp + "/target/furyctl.yaml")). + To(Equal(FileContent(bp + "/data/expected-furyctl.yaml"))) + }) + }) + + Context("cluster creation, dry run", Ordered, Serial, Label("slow"), func() { + var w string + var absBasePath string + + basepath := "../data/e2e/create/cluster" + + FuryctlCreateCluster := func(cfgPath, distroPath, phase string, dryRun bool) *exec.Cmd { + patchedCfgPath, err := patchFuryctlYaml(cfgPath) + if err != nil { + panic(err) + } + + args := []string{ + "create", + "cluster", + "--config", + patchedCfgPath, + "--distro-location", + distroPath, + "--debug", + "--workdir", + w, + "--disable-analytics", + "true", + } + + if phase != "" { + args = append(args, "--phase", phase) + } else { + args = append(args, "--vpn-auto-connect") + } + + if dryRun { + args = append(args, "--dry-run") + } + + return exec.Command(furyctl, args...) + } + + BeforeEach(func() { + var err error + + absBasePath, err = filepath.Abs(basepath) + Expect(err).To(Not(HaveOccurred())) + + w, err = os.MkdirTemp("", "create-cluster-test-") + Expect(err).To(Not(HaveOccurred())) + + Expect(w).To(BeADirectory()) + + DeferCleanup(func() error { + return os.RemoveAll(w) + }) + }) + + It("should execute a dry run of the cluster creation's infrastructure phase", func() { + RestoreEnvVars := BackupEnvVars("PATH") + defer RestoreEnvVars() + + bp := Abs("../data/e2e/create/cluster/bin_mock") + + err := os.Setenv("PATH", bp+":"+os.Getenv("PATH")) + Expect(err).To(Not(HaveOccurred())) + + furyctlYamlPath := path.Join(absBasePath, "data/furyctl.yaml") + distroPath := path.Join(absBasePath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + tfPath := path.Join(homeDir, ".furyctl", "furyctl-dev-aws", cluster.OperationPhaseInfrastructure, "terraform") + + createInfraCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseInfrastructure, true) + session, err := gexec.Start(createInfraCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(path.Join(tfPath, "plan", "terraform.plan"), 120*time.Second).Should(BeAnExistingFile()) + + Eventually(session).Should(gexec.Exit(0)) + + _, err = FindFileStartingWith(path.Join(tfPath, "plan"), "plan-") + Expect(err).To(Not(HaveOccurred())) + }) + + It("should execute a dry run of the cluster creation's kubernetes phase", func() { + RestoreEnvVars := BackupEnvVars("PATH") + defer RestoreEnvVars() + + bp := Abs("../data/e2e/create/cluster/bin_mock") + + err := os.Setenv("PATH", bp+":"+os.Getenv("PATH")) + Expect(err).To(Not(HaveOccurred())) + + furyctlYamlPath := path.Join(absBasePath, "data/furyctl.yaml") + distroPath := path.Join(absBasePath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + tfPath := path.Join(homeDir, ".furyctl", "furyctl-dev-aws", cluster.OperationPhaseKubernetes, "terraform") + + createKubeCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseKubernetes, true) + session, err := gexec.Start(createKubeCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(path.Join(tfPath, "plan", "terraform.plan"), 120*time.Second).Should(BeAnExistingFile()) + + Eventually(session).Should(gexec.Exit(0)) + + _, err = FindFileStartingWith(path.Join(tfPath, "plan"), "plan-") + Expect(err).To(Not(HaveOccurred())) + }) + }) + }) +) + +// patch the furyctl.yaml's "spec.toolsConfiguration.terraform.state.s3.keyPrefix" key to add a timestamp and random int +// to avoid collisions in s3 when running tests in parallel, and also because the bucket is a super global resource. +func patchFuryctlYaml(furyctlYamlPath string) (string, error) { + furyctlYaml, err := os.ReadFile(furyctlYamlPath) + if err != nil { + return "", err + } + + // we need to cap the string to 36 chars due to the s3 key prefix limit + newKeyPrefix := fmt.Sprintf("furyctl-%d-%d", time.Now().UTC().Unix(), rand.Int())[0:36] + + furyctlYaml = bytes.ReplaceAll(furyctlYaml, []byte("keyPrefix: furyctl/"), []byte("keyPrefix: "+newKeyPrefix+"/")) + + // create a temporary file to write the patched furyctl.yaml + tmpFile, err := os.CreateTemp("", "furyctl.yaml") + if err != nil { + return "", err + } + + _, err = tmpFile.Write(furyctlYaml) + + return tmpFile.Name(), err +} diff --git a/test/expensive/furyctl_test.go b/test/expensive/furyctl_test.go new file mode 100644 index 000000000..74ad59ac8 --- /dev/null +++ b/test/expensive/furyctl_test.go @@ -0,0 +1,325 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build expensive + +package expensive_test + +import ( + "os" + "os/exec" + "path" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" + + "github.com/sighupio/furyctl/internal/cluster" +) + +func TestExpensive(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Furyctl Expensive Suite") +} + +var ( + furyctl string + + basePath = "../data/expensive" + + _ = BeforeSuite(func() { + tmpdir, err := os.MkdirTemp("", "furyctl-expensive") + Expect(err).To(Not(HaveOccurred())) + + furyctl = filepath.Join(tmpdir, "furyctl") + + cmd := exec.Command("go", "build", "-o", furyctl, "../../main.go") + + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + Eventually(session, 5*time.Minute).Should(gexec.Exit(0)) + }) + + CreatePaths = func(dir string) (string, string, string) { + absBasePath, err := filepath.Abs(basePath) + Expect(err).To(Not(HaveOccurred())) + + common := path.Join(absBasePath, "common") + + workDir := path.Join(absBasePath, dir) + + w, err := os.MkdirTemp("", "create-cluster-test-") + Expect(err).To(Not(HaveOccurred())) + + Expect(w).To(BeADirectory()) + + return workDir, common, w + } + + FuryctlDeleteCluster = func(cfgPath, distroPath, phase string, dryRun bool, w string) *exec.Cmd { + args := []string{ + "delete", + "cluster", + "--config", + cfgPath, + "--distro-location", + distroPath, + "--debug", + "--force", + "--workdir", + w, + } + + if phase != cluster.OperationPhaseAll { + args = append(args, "--phase", phase) + } + + if dryRun { + args = append(args, "--dry-run") + } + + return exec.Command(furyctl, args...) + } + + FuryctlCreateCluster = func(cfgPath, distroPath, phase, skipPhase string, dryRun bool, w string) *exec.Cmd { + args := []string{ + "create", + "cluster", + "--config", + cfgPath, + "--distro-location", + distroPath, + "--debug", + "--workdir", + w, + } + + if phase != cluster.OperationPhaseAll { + args = append(args, "--phase", phase) + } + + if phase == cluster.OperationPhaseInfrastructure { + args = append(args, "--vpn-auto-connect") + } + + if skipPhase != "" { + args = append(args, "--skip-phase", skipPhase) + } + + if dryRun { + args = append(args, "--dry-run") + } + + return exec.Command(furyctl, args...) + } + + KillOpenVPN = func() (*gexec.Session, error) { + cmd := exec.Command("sudo", "pkill", "openvpn") + + return gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + } + + _ = Describe("furyctl", Ordered, Serial, func() { + _ = AfterEach(func() { + if CurrentSpecReport().Failed() { + GinkgoWriter.Write([]byte("Test failed, cleaning up...")) + } + }) + + Context("cluster creation and cleanup", Ordered, Serial, Label("slow"), func() { + absWorkDirPath, absCommonPath, w := CreatePaths("create-complete") + + It("should create a complete cluster", Serial, func() { + furyctlYamlPath := path.Join(absWorkDirPath, "data/furyctl.yaml") + distroPath := path.Join(absCommonPath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + kubeBinPath := path.Join(homeDir, ".furyctl", "bin", "kubectl", "1.24.7", "kubectl") + tfInfraPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws", cluster.OperationPhaseInfrastructure, "terraform") + kcfgPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws", cluster.OperationPhaseKubernetes, "terraform", "secrets", "kubeconfig") + + createClusterCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, "", false, w) + + session, err := gexec.Start(createClusterCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + Consistently(session, 3*time.Minute).ShouldNot(gexec.Exit()) + Expect(path.Join(tfInfraPath, "plan", "terraform.plan")).To(BeAnExistingFile()) + Eventually(kcfgPath, 20*time.Minute).Should(BeAnExistingFile()) + Eventually(session, 40*time.Minute).Should(gexec.Exit(0)) + + kubeCmd := exec.Command(kubeBinPath, "--kubeconfig", kcfgPath, "get", "nodes") + + kubeSession, err := gexec.Start(kubeCmd, GinkgoWriter, GinkgoWriter) + + Expect(err).To(Not(HaveOccurred())) + Eventually(kubeSession, 2*time.Minute).Should(gexec.Exit(0)) + }) + + It("should destroy a cluster", Serial, func() { + furyctlYamlPath := path.Join(absWorkDirPath, "data/furyctl.yaml") + distroPath := path.Join(absCommonPath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + kcfgPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws", cluster.OperationPhaseKubernetes, "terraform", "secrets", "kubeconfig") + + err = os.Setenv("KUBECONFIG", kcfgPath) + Expect(err).To(Not(HaveOccurred())) + + DeferCleanup(func() { + _ = os.Unsetenv("KUBECONFIG") + _ = os.RemoveAll(w) + pkillSession, err := KillOpenVPN() + Expect(err).To(Not(HaveOccurred())) + Eventually(pkillSession, 10*time.Second).Should(gexec.Exit(0)) + }) + + deleteClusterCmd := FuryctlDeleteCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, false, w) + + session, err := gexec.Start(deleteClusterCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(session, 40*time.Minute).Should(gexec.Exit(0)) + }) + }) + + Context("cluster creation skipping infra phase, and cleanup", Ordered, Serial, Label("slow"), func() { + absWorkDirPath, absCommonPath, w := CreatePaths("create-skip-infra") + + It("should create a cluster, skipping the infrastructure phase", Serial, func() { + furyctlYamlPath := path.Join(absWorkDirPath, "data/furyctl.yaml") + distroPath := path.Join(absCommonPath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + kubeBinPath := path.Join(homeDir, ".furyctl", "bin", "kubectl", "1.24.7", "kubectl") + kcfgPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws-si", cluster.OperationPhaseKubernetes, "terraform", "secrets", "kubeconfig") + + createClusterInfraCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseInfrastructure, "", false, w) + + infraSession, err := gexec.Start(createClusterInfraCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(infraSession, 20*time.Minute).Should(gexec.Exit(0)) + + createClusterCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, cluster.OperationPhaseInfrastructure, false, w) + + session, err := gexec.Start(createClusterCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + Consistently(session, 3*time.Minute).ShouldNot(gexec.Exit()) + Eventually(kcfgPath, 20*time.Minute).Should(BeAnExistingFile()) + Eventually(session, 40*time.Minute).Should(gexec.Exit(0)) + + kubeCmd := exec.Command(kubeBinPath, "--kubeconfig", kcfgPath, "get", "nodes") + + kubeSession, err := gexec.Start(kubeCmd, GinkgoWriter, GinkgoWriter) + + Expect(err).To(Not(HaveOccurred())) + Eventually(kubeSession, 2*time.Minute).Should(gexec.Exit(0)) + }) + + It("should destroy a cluster", Serial, func() { + furyctlYamlPath := path.Join(absWorkDirPath, "data/furyctl.yaml") + distroPath := path.Join(absCommonPath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + kcfgPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws-si", cluster.OperationPhaseKubernetes, "terraform", "secrets", "kubeconfig") + + err = os.Setenv("KUBECONFIG", kcfgPath) + Expect(err).To(Not(HaveOccurred())) + + DeferCleanup(func() { + _ = os.Unsetenv("KUBECONFIG") + _ = os.RemoveAll(w) + pkillSession, err := KillOpenVPN() + Expect(err).To(Not(HaveOccurred())) + Eventually(pkillSession, 10*time.Second).Should(gexec.Exit(0)) + }) + + deleteClusterCmd := FuryctlDeleteCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, false, w) + + session, err := gexec.Start(deleteClusterCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(session, 40*time.Minute).Should(gexec.Exit(0)) + }) + }) + + Context("cluster creation skipping kubernetes phase, and cleanup", Ordered, Serial, Label("slow"), func() { + absWorkDirPath, absCommonPath, w := CreatePaths("create-skip-kube") + + It("should create a cluster, skipping the kubernetes phase", Serial, func() { + furyctlYamlPath := path.Join(absWorkDirPath, "data/furyctl.yaml") + distroPath := path.Join(absCommonPath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + kubeBinPath := path.Join(homeDir, ".furyctl", "bin", "kubectl", "1.24.7", "kubectl") + kcfgPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws-sk", cluster.OperationPhaseKubernetes, "terraform", "secrets", "kubeconfig") + + createClusterKubeCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, cluster.OperationPhaseDistribution, false, w) + + kubeSession, err := gexec.Start(createClusterKubeCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(kubeSession, 20*time.Minute).Should(gexec.Exit(0)) + + err = os.Setenv("KUBECONFIG", kcfgPath) + Expect(err).To(Not(HaveOccurred())) + + createClusterCmd := FuryctlCreateCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, cluster.OperationPhaseKubernetes, false, w) + + session, err := gexec.Start(createClusterCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + Consistently(session, 3*time.Minute).ShouldNot(gexec.Exit()) + Eventually(session, 40*time.Minute).Should(gexec.Exit(0)) + + kubeCmd := exec.Command(kubeBinPath, "--kubeconfig", kcfgPath, "get", "nodes") + + kubectlSession, err := gexec.Start(kubeCmd, GinkgoWriter, GinkgoWriter) + + Expect(err).To(Not(HaveOccurred())) + Eventually(kubectlSession, 2*time.Minute).Should(gexec.Exit(0)) + }) + + It("should destroy a cluster", Serial, func() { + furyctlYamlPath := path.Join(absWorkDirPath, "data/furyctl.yaml") + distroPath := path.Join(absCommonPath, "data") + + homeDir, err := os.UserHomeDir() + Expect(err).To(Not(HaveOccurred())) + + kcfgPath := path.Join(homeDir, ".furyctl", "furyctl-test-aws-sk", cluster.OperationPhaseKubernetes, "terraform", "secrets", "kubeconfig") + + err = os.Setenv("KUBECONFIG", kcfgPath) + Expect(err).To(Not(HaveOccurred())) + + DeferCleanup(func() { + _ = os.Unsetenv("KUBECONFIG") + _ = os.RemoveAll(w) + pkillSession, err := KillOpenVPN() + Expect(err).To(Not(HaveOccurred())) + Eventually(pkillSession, 10*time.Second).Should(gexec.Exit(0)) + }) + + deleteClusterCmd := FuryctlDeleteCluster(furyctlYamlPath, distroPath, cluster.OperationPhaseAll, false, w) + + session, err := gexec.Start(deleteClusterCmd, GinkgoWriter, GinkgoWriter) + Expect(err).To(Not(HaveOccurred())) + + Eventually(session, 40*time.Minute).Should(gexec.Exit(0)) + }) + }) + }) +) diff --git a/test/helper.bash b/test/helper.bash deleted file mode 100644 index f5354918c..000000000 --- a/test/helper.bash +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bats - -info(){ - echo -e "${BATS_TEST_NUMBER}: ${BATS_TEST_DESCRIPTION}" >&3 -} - -loop_it(){ - retry_counter=0 - max_retry=${2:-100} - wait_time=${3:-2} - run ${1} - ko=${status} - loop_it_result=${ko} - while [[ ko -ne 0 ]] - do - if [ $retry_counter -ge $max_retry ]; then echo "Timeout waiting a condition"; return 1; fi - sleep ${wait_time} && echo "# waiting..." $retry_counter >&3 - run ${1} - ko=${status} - loop_it_result=${ko} - retry_counter=$((retry_counter + 1)) - done - return 0 -} diff --git a/test/integration/template-engine/.gitignore b/test/integration/template-engine/.gitignore deleted file mode 100644 index eb5a316cb..000000000 --- a/test/integration/template-engine/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/test/integration/template-engine/data/complex-dry-run/source/kustomization.yaml.tpl b/test/integration/template-engine/data/complex-dry-run/source/kustomization.yaml.tpl deleted file mode 100644 index 19922bd2b..000000000 --- a/test/integration/template-engine/data/complex-dry-run/source/kustomization.yaml.tpl +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -{{ if .modules.ingress }} -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - {{ .common.relativeVendorPath }}/katalog/ingress/cert-manager -{{- if eq .modules.ingress.nginx.type "dual" }} - - {{ .common.relativeVendorPath }}/katalog/ingress/dual-nginx -{{- end }} -{{- if eq .modules.ingress.nginx.type "single" }} - - {{ .common.relativeVendorPath }}/katalog/ingress/nginx -{{- end }} - - {{ .common.relativeVendorPath }}/katalog/ingress/forecastle -{{- if .modules.ingress.certManager.clusterIssuer.notExistingProperty }} - - resources/cert-manager-clusterissuer.yml -{{- end }} - -patchesStrategicMerge: -{{- if .modules.ingress.certManager.clusterIssuer.notExistingProperty }} - - patches/cert-manager.yml -{{- end }} - - patches/infra-nodes.yml - - patches/ingress-nginx.yml -{{- end }} diff --git a/test/integration/template-engine/data/complex/source/kustomization.yaml.tpl b/test/integration/template-engine/data/complex/source/kustomization.yaml.tpl deleted file mode 100644 index 283a59c7d..000000000 --- a/test/integration/template-engine/data/complex/source/kustomization.yaml.tpl +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -{{ if .modules.ingress }} -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - {{ .common.relativeVendorPath }}/katalog/ingress/cert-manager -{{- if eq .modules.ingress.nginx.type "dual" }} - - {{ .common.relativeVendorPath }}/katalog/ingress/dual-nginx -{{- end }} -{{- if eq .modules.ingress.nginx.type "single" }} - - {{ .common.relativeVendorPath }}/katalog/ingress/nginx -{{- end }} - - {{ .common.relativeVendorPath }}/katalog/ingress/forecastle -{{- if .modules.ingress.certManager.clusterIssuer.name }} - - resources/cert-manager-clusterissuer.yml -{{- end }} - -patchesStrategicMerge: -{{- if .modules.ingress.certManager.clusterIssuer.name }} - - patches/cert-manager.yml -{{- end }} - - patches/infra-nodes.yml - - patches/ingress-nginx.yml -{{- end }} diff --git a/test/integration/template-engine/data/distribution-yaml-no-data-property/source/file.txt.tpl b/test/integration/template-engine/data/distribution-yaml-no-data-property/source/file.txt.tpl deleted file mode 100644 index 80f72d16b..000000000 --- a/test/integration/template-engine/data/distribution-yaml-no-data-property/source/file.txt.tpl +++ /dev/null @@ -1 +0,0 @@ -{{.test.hello}} diff --git a/test/integration/template-engine/data/simple-dry-run/source/file.txt.tpl b/test/integration/template-engine/data/simple-dry-run/source/file.txt.tpl deleted file mode 100644 index 80f72d16b..000000000 --- a/test/integration/template-engine/data/simple-dry-run/source/file.txt.tpl +++ /dev/null @@ -1 +0,0 @@ -{{.test.hello}} diff --git a/test/integration/template-engine/data/simple/source/file.txt.tpl b/test/integration/template-engine/data/simple/source/file.txt.tpl deleted file mode 100644 index 80f72d16b..000000000 --- a/test/integration/template-engine/data/simple/source/file.txt.tpl +++ /dev/null @@ -1 +0,0 @@ -{{.test.hello}} diff --git a/test/integration/template-engine/tests.sh b/test/integration/template-engine/tests.sh deleted file mode 100644 index 3acbad119..000000000 --- a/test/integration/template-engine/tests.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bats -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -load "./../../helper" - -OS="linux" -if [[ "${OSTYPE}" == "darwin"* ]]; then - OS="darwin" -fi -CPUARCH="amd64_v1" -# if [ "$(uname -m)" = "arm64" ]; then -# CPUARCH="arm64" -# fi - -FURYCTL="${PWD}/dist/furyctl" - -@test "prepare" { - info - init(){ - go build -o ${PWD}/dist/furyctl ${PWD}/main.go - } - run init -} - -@test "furyctl" { - info - init(){ - ${FURYCTL} version - } - run init - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "no distribution file" { - info - test_dir="./test/integration/template-engine/data/no-distribution-yaml" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template - } - run init - - [ "${status}" -eq 1 ] - - if [[ ${output} != *"distribution.yaml: no such file or directory"* ]]; then - echo "${output}" >&3 - fi - [[ "${output}" == *"distribution.yaml: no such file or directory"* ]] -} - -@test "no furyctl.yaml file" { - info - test_dir="./test/integration/template-engine/data/no-furyctl-yaml" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template - } - run init - - [ "${status}" -eq 1 ] - - if [[ ${output} != *"furyctl.yaml: no such file or directory"* ]]; then - echo "${output}" >&3 - fi - [[ "${output}" == *"furyctl.yaml: no such file or directory"* ]] -} - -@test "no data property in distribution.yaml file" { - info - test_dir="./test/integration/template-engine/data/distribution-yaml-no-data-property" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template - } - run init - - [ "${status}" -eq 1 ] - - if [[ ${output} != *"incorrect base file, cannot access key data on map"* ]]; then - echo "${output}" >&3 - fi - [[ "${output}" == *"incorrect base file, cannot access key data on map"* ]] -} - -@test "empty template" { - info - test_dir="./test/integration/template-engine/data/empty" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template - if [ -f ./target/file.txt ]; then false; else true; fi - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "simple template dry-run" { - info - test_dir="./test/integration/template-engine/data/simple-dry-run" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template --dry-run - cat ./target/file.txt | grep "testValue" - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "simple template" { - info - test_dir="./test/integration/template-engine/data/simple" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template - cat ./target/file.txt | grep "testValue" - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "complex template dry-run" { - info - test_dir="./test/integration/template-engine/data/complex" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template --dry-run - - # test that the config/example.yaml file has been generated - if [ ! -f ./target/config/example.yaml ]; then - echo "config/example.yaml file not generated" >&3 - false - fi - - # test that the kustomization.yaml file has been generated - if [ ! -f ./target/kustomization.yaml ]; then - echo "kustomization.yaml file not generated" >&3 - false - fi - - # test that the config/example.yaml contains the string "configdata: example" - if ! grep -q "configdata: example" ./target/config/example.yaml; then - echo "config/example.yaml file does not contain the string 'configdata: example'" >&3 - false - fi - - # test that the kustomization.yaml contains the same data of data/expected-kustomization.yaml - if ! diff -q ./target/kustomization.yaml ./data/expected-kustomization.yaml; then - echo -e "kustomization.yaml file does not contain the same data of data/expected-kustomization.yaml, Diff:" >&3 - diff ./target/kustomization.yaml ./data/expected-kustomization.yaml >&3 - false - fi - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} - -@test "complex template" { - info - test_dir="./test/integration/template-engine/data/complex" - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug dump template - - # test that the config/example.yaml file has been generated - if [ ! -f ./target/config/example.yaml ]; then - echo "config/example.yaml file not generated" >&3 - false - fi - - # test that the kustomization.yaml file has been generated - if [ ! -f ./target/kustomization.yaml ]; then - echo "kustomization.yaml file not generated" >&3 - false - fi - - # test that the config/example.yaml contains the string "configdata: example" - if ! grep -q "configdata: example" ./target/config/example.yaml; then - echo "config/example.yaml file does not contain the string 'configdata: example'" >&3 - false - fi - - # test that the kustomization.yaml contains the same data of data/expected-kustomization.yaml - if ! diff -q ./target/kustomization.yaml ./data/expected-kustomization.yaml; then - echo -e "kustomization.yaml file does not contain the same data of data/expected-kustomization.yaml, Diff:" >&3 - diff ./target/kustomization.yaml ./data/expected-kustomization.yaml >&3 - false - fi - } - run init - - if [[ ${status} -ne 0 ]]; then - echo "${output}" >&3 - fi - [ "${status}" -eq 0 ] -} diff --git a/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json deleted file mode 100644 index 3edea0860..000000000 --- a/test/integration/validation-cmd/data/config-invalid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json +++ /dev/null @@ -1,1314 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", - "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", - "type": "object", - "properties": { - "apiVersion": { - "type": "string", - "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" - }, - "kind": { - "type": "string", - "enum": ["EKSCluster"] - }, - "metadata": { - "$ref": "#/$defs/Metadata" - }, - "spec": { - "$ref": "#/$defs/Spec" - } - }, - "additionalProperties": false, - "required": [ - "apiVersion", - "kind", - "metadata", - "spec" - ], - "$defs": { - "Metadata": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ] - }, - "Spec": { - "type": "object", - "additionalProperties": false, - "properties": { - "distributionVersion": { - "$ref": "#/$defs/Types.SemVer" - }, - "tags": { - "$ref": "#/$defs/Types.AwsTags" - }, - "toolsConfiguration": { - "$ref": "#/$defs/Spec.ToolsConfiguration" - }, - "infrastructure": { - "$ref": "#/$defs/Spec.Infrastructure" - }, - "kubernetes": { - "type": "object", - "additionalProperties": true - }, - "distribution": { - "$ref": "#/$defs/Spec.Distribution" - } - }, - "required": [ - "distributionVersion", - "infrastructure", - "kubernetes", - "toolsConfiguration" - ] - }, - - "Spec.ToolsConfiguration": { - "type": "object", - "additionalProperties": false, - "properties": { - "terraform": { - "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" - } - }, - "required": [ - "terraform" - ] - }, - "Spec.ToolsConfiguration.Terraform": { - "type": "object", - "additionalProperties": false, - "properties": { - "state": { - "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" - } - }, - "required": [ - "state" - ] - }, - "Spec.ToolsConfiguration.Terraform.State": { - "type": "object", - "additionalProperties": false, - "properties": { - "s3": { - "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" - } - }, - "required": [ - "s3" - ] - }, - "Spec.ToolsConfiguration.Terraform.State.S3": { - "type": "object", - "additionalProperties": false, - "properties": { - "bucketName": { - "type": "string" - }, - "keyPrefix": { - "type": "string" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - } - }, - "required": [ - "bucketName", - "keyPrefix", - "region" - ] - }, - - "Spec.Infrastructure": { - "type": "object", - "additionalProperties": false, - "properties": { - "vpc": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc" - } - }, - "required": [ - "vpc" - ] - }, - "Spec.Infrastructure.Vpc": { - "type": "object", - "additionalProperties": false, - "properties": { - "network": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" - }, - "vpn": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" - } - }, - "required": [ - "network" - ] - }, - "Spec.Infrastructure.Vpc.Network": { - "type": "object", - "additionalProperties": false, - "properties": { - "cidr": { - "$ref": "#/$defs/Types.Cidr" - }, - "subnetsCidrs": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" - } - }, - "required": [ - "cidr", - "subnetsCidrs" - ] - }, - "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { - "type": "object", - "additionalProperties": false, - "properties": { - "private": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - }, - "public": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - } - }, - "required": [ - "private", - "public" - ] - }, - "Spec.Infrastructure.Vpc.Vpn": { - "type": "object", - "additionalProperties": false, - "properties": { - "instances": { - "type": "integer" - }, - "port": { - "$ref": "#/$defs/Types.TcpPort" - }, - "instanceType": { - "type": "string" - }, - "diskSize": { - "type": "integer" - }, - "operatorName": { - "type": "string" - }, - "dhParamsBits": { - "type": "integer" - }, - "vpnClientsSubnetCidr": { - "$ref": "#/$defs/Types.Cidr" - }, - "ssh": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" - } - }, - "required": [ - "dhParamsBits", - "diskSize", - "instances", - "instanceType", - "operatorName", - "port", - "ssh", - "vpnClientsSubnetCidr" - ] - }, - "Spec.Infrastructure.Vpc.Vpn.Ssh": { - "type": "object", - "additionalProperties": false, - "properties": { - "publicKeys": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/$defs/Types.SshPubKey" - }, - { - "$ref": "#/$defs/Types.FileRef" - } - ] - } - }, - "githubUsersName": { - "type": "array", - "items": { - "type": "string" - } - }, - "allowedFromCidrs": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - } - }, - "required": [ - "allowedFromCidrs", - "githubUsersName", - "publicKeys" - ] - }, - - "Spec.Kubernetes": { - "type": "object", - "additionalProperties": false, - "properties": { - "vpcId": { - "$ref": "#/$defs/Types.AwsVpcId" - }, - "subnetIds": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.AwsSubnetId" - } - }, - "apiServerEndpointAccess": { - "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" - }, - "nodeAllowedSshPublicKey": { - "anyOf": [ - { - "$ref": "#/$defs/Types.SshPubKey" - }, - { - "$ref": "#/$defs/Types.FileRef" - } - ] - }, - "nodePools": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool" - } - }, - "awsAuth": { - "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" - } - }, - "required": [ - "apiServerEndpointAccess", - "awsAuth", - "nodeAllowedSshPublicKey", - "nodePools", - "subnetIds", - "vpcId" - ] - }, - "Spec.Kubernetes.APIServerEndpointAccess": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": [ - "public", - "private", - "public_and_private" - ] - }, - "allowedCidrs": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - } - }, - "required": [ - "allowedCidrs", - "type" - ] - }, - "Spec.Kubernetes.NodePool": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "ami": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" - }, - "size": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" - }, - "instance": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" - }, - "attachedTargetGroups": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "labels": { - "$ref": "#/$defs/Types.KubeLabels" - }, - "taints": { - "$ref": "#/$defs/Types.KubeTaints" - }, - "tags": { - "$ref": "#/$defs/Types.AwsTags" - }, - "additionalFirewallRules": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" - } - } - }, - "required": [ - "additionalFirewallRules", - "ami", - "attachedTargetGroups", - "instance", - "labels", - "name", - "size", - "tags", - "taints" - ] - }, - "Spec.Kubernetes.NodePool.Ami": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "owner": { - "type": "string" - } - }, - "required": [ - "id", - "owner" - ] - }, - "Spec.Kubernetes.NodePool.Instance": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string" - }, - "spot": { - "type": "boolean" - }, - "volumeSize": { - "type": "integer" - } - }, - "required": [ - "spot", - "type", - "volumeSize" - ] - }, - "Spec.Kubernetes.NodePool.Size": { - "type": "object", - "additionalProperties": false, - "properties": { - "min": { - "type": "integer", - "minimum": 1 - }, - "max": { - "type": "integer", - "minimum": 1 - } - }, - "required": [ - "max", - "min" - ] - }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "cidrBlocks": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - }, - "protocol": { - "$ref": "#/$defs/Types.AwsIpProtocol" - }, - "ports": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" - } - }, - "required": [ - "cidrBlocks", - "name", - "ports", - "protocol", - "type" - ] - }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { - "type": "object", - "additionalProperties": false, - "properties": { - "from": { - "$ref": "#/$defs/Types.TcpPort" - }, - "to": { - "$ref": "#/$defs/Types.TcpPort" - } - }, - "required": [ - "from", - "to" - ] - }, - "Spec.Kubernetes.AwsAuth": { - "type": "object", - "additionalProperties": false, - "properties": { - "additionalAccounts": { - "type": "array", - "items": { - "type": "string" - } - }, - "users": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" - } - }, - "roles": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" - } - } - }, - "required": [ - "additionalAccounts", - "roles", - "users" - ] - }, - "Spec.Kubernetes.AwsAuth.Role": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string" - }, - "groups": { - "type": "array", - "items": { - "type": "string" - } - }, - "rolearn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "groups", - "rolearn", - "username" - ] - }, - "Spec.Kubernetes.AwsAuth.User": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string" - }, - "groups": { - "type": "array", - "items": { - "type": "string" - } - }, - "userarn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "groups", - "userarn", - "username" - ] - }, - - "Spec.Distribution": { - "type": "object", - "additionalProperties": false, - "properties": { - "common": { - "$ref": "#/$defs/Spec.Distribution.Common" - }, - "modules": { - "$ref": "#/$defs/Spec.Distribution.Modules" - } - }, - "required": [ - "common", - "modules" - ] - }, - "Spec.Distribution.Common": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeSelector": { - "$ref": "#/$defs/Types.KubeNodeSelector" - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.KubeToleration" - } - }, - "provider": { - "$ref": "#/$defs/Spec.Distribution.Common.Provider" - }, - "relativeVendorPath": { - "type": "string" - } - }, - "required": [ - "nodeSelector", - "tolerations", - "provider", - "relativeVendorPath" - ] - }, - "Spec.Distribution.Common.Provider": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string" - } - } - }, - "Spec.Distribution.Modules": { - "type": "object", - "additionalProperties": false, - "properties": { - "auth": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth" - }, - "dr": { - "$ref": "#/$defs/Spec.Distribution.Modules.Dr" - }, - "ingress": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" - }, - "logging": { - "$ref": "#/$defs/Spec.Distribution.Modules.Logging" - }, - "monitoring": { - "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" - }, - "policy": { - "$ref": "#/$defs/Spec.Distribution.Modules.Policy" - }, - "aws": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws" - } - }, - "required": [ - "auth", - "dr", - "ingress", - "logging", - "monitoring", - "policy" - ] - }, - "Spec.Distribution.Modules.Ingress": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "baseDomain": { - "type": "string" - }, - "nginx": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" - }, - "certManager": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CERTManager" - }, - "dns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" - } - }, - "required": [ - "baseDomain", - "certManager", - "dns", - "nginx", - "overrides" - ] - }, - "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { - "type": "object", - "additionalProperties": false, - "properties": { - "forecastle": { - "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" - } - } - }, - "Spec.Distribution.Modules.Ingress.Nginx": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": ["single", "dual"] - }, - "tls": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" - } - }, - "required": [ - "tls", - "type" - ] - }, - "Spec.Distribution.Modules.Ingress.Nginx.TLS": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "type": "string", - "enum": ["certManager", "secret", "none"] - }, - "secret": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" - } - }, - "required": [ - "provider", - "secret" - ] - }, - "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { - "type": "object", - "additionalProperties": false, - "properties": { - "cert": { - "type": "string" - }, - "key": { - "type": "string" - }, - "ca": { - "type": "string" - } - }, - "required": [ - "ca", - "cert", - "key" - ] - }, - "Spec.Distribution.Modules.Ingress.CERTManager": { - "type": "object", - "additionalProperties": false, - "properties": { - "clusterIssuer": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer" - } - }, - "required": [ - "clusterIssuer" - ] - }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" - } - }, - "required": [ - "name", - "type" - ] - }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.DNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "public": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" - }, - "private": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" - } - }, - "required": [ - "public", - "private" - ] - }, - "Spec.Distribution.Modules.Ingress.DNS.Public": { - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "create": { - "type": "boolean" - } - }, - "required": [ - "enabled", - "name", - "create" - ] - }, - "Spec.Distribution.Modules.Ingress.DNS.Private": { - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "vpcId": { - "type": "string" - } - }, - "required": [ - "enabled", - "name", - "vpcId" - ] - }, - "Spec.Distribution.Modules.Logging": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "opensearch": { - "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" - } - } - }, - "Spec.Distribution.Modules.Logging.Opensearch": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": ["single", "triple"] - }, - "resources": { - "$ref": "#/$defs/Types.KubeResources" - }, - "storage_request": { - "type": "string" - } - }, - "required": [ - "type" - ] - }, - "Spec.Distribution.Modules.Monitoring": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "prometheus": { - "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" - } - } - }, - "Spec.Distribution.Modules.Monitoring.Prometheus": { - "type": "object", - "additionalProperties": false, - "properties": { - "resources": { - "$ref": "#/$defs/Types.KubeResources" - } - } - }, - "Spec.Distribution.Modules.Policy": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "gatekeeper": { - "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" - } - } - }, - "Spec.Distribution.Modules.Policy.Gatekeeper": { - "type": "object", - "additionalProperties": false, - "properties": { - "additionalExcludedNamespaces": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Spec.Distribution.Modules.Dr": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "velero": { - "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" - } - } - }, - "Spec.Distribution.Modules.Dr.Velero": { - "type": "object", - "additionalProperties": false, - "properties": { - "eks": { - "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" - } - } - }, - "Spec.Distribution.Modules.Dr.Velero.Eks": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "bucket": { - "type": "string" - } - } - }, - "Spec.Distribution.Modules.Auth": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" - }, - "provider": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" - }, - "pomerium": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" - }, - "dex": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" - } - } - }, - - "Spec.Distribution.Modules.Auth.Overrides": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeSelector": { - "$ref": "#/$defs/Types.KubeNodeSelector" - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.KubeToleration" - } - }, - "ingresses": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingresses" - } - } - } - }, - "Spec.Distribution.Modules.Auth.Overrides.Ingresses": { - "type": "object", - "additionalProperties": false, - "properties": { - "host": { - "type": "string" - }, - "ingressClass": { - "type": "string" - } - }, - "required": [ - "host", - "ingressClass" - ] - }, - "Spec.Distribution.Modules.Auth.Provider": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": ["none", "basicAuth", "sso"] - }, - "basicAuth": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" - } - }, - "required": [ - "type" - ] - }, - "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "required": [ - "username", - "password" - ] - }, - "Spec.Distribution.Modules.Auth.Pomerium": { - "type": "object", - "additionalProperties": false, - "properties": { - "secrets": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" - } - }, - "required": [ - "secrets" - ] - }, - "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { - "type": "object", - "additionalProperties": false, - "properties": { - "COOKIE_SECRET": { - "type": "string" - }, - "IDP_CLIENT_SECRET": { - "type": "string" - }, - "SHARED_SECRET": { - "type": "string" - } - }, - "required": [ - "COOKIE_SECRET", - "IDP_CLIENT_SECRET", - "SHARED_SECRET" - ] - }, - "Spec.Distribution.Modules.Auth.Dex": { - "type": "object", - "additionalProperties": false, - "properties": { - "connectors": { - "type": "array" - } - } - }, - "Spec.Distribution.Modules.Aws": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" - } - } - }, - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeGroupAutoDiscovery": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "iamRoleArn", - "nodeGroupAutoDiscovery" - ] - }, - - "Types.SemVer": { - "type": "string", - "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" - }, - "Types.IpAddress": { - "type": "string", - "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" - }, - "Types.Cidr": { - "type": "string", - "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" - }, - "Types.FileRef": { - "type": "string", - "pattern": "^\\{file\\:\\/\\/.+\\}$" - }, - "Types.EnvRef": { - "type": "string", - "pattern": "\\{^env\\:\\/\\/.*\\}$" - }, - "Types.TcpPort": { - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "Types.SshPubKey": { - "type": "string", - "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" - }, - "Types.Uri": { - "type": "string", - "pattern": "^(http|https)\\:\\/\\/.+$" - }, - "Types.AwsArn": { - "type": "string", - "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" - }, - "Types.AwsRegion": { - "type": "string", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ca-central-1", - "eu-central-1", - "eu-north-1", - "eu-south-1", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2" - ] - }, - "Types.AwsVpcId": { - "type": "string", - "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{16})$" - }, - "Types.AwsSubnetId": { - "type": "string", - "pattern": "^subnet\\-[0-9a-f]{16}$" - }, - "Types.AwsTags": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "Types.AwsIpProtocol": { - "type": "string", - "enum": ["tcp", "udp", "icmp", "icmpv6", "-1"] - }, - "Types.KubeLabels": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "Types.KubeTaints": { - "type": "array", - "items": { - "type": "string", - "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" - } - }, - "Types.KubeNodeSelector": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "Types.KubeToleration": { - "type": "object", - "additionalProperties": false, - "properties": { - "effect": { - "type": "string", - "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] - }, - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "effect", - "key", - "value" - ] - }, - "Types.KubeResources": { - "type": "object", - "additionalProperties": false, - "properties": { - "requests": { - "type": "object", - "additionalProperties": false, - "properties": { - "cpu": { - "type": "string" - }, - "memory": { - "type": "string" - } - } - }, - "limits": { - "type": "object", - "additionalProperties": false, - "properties": { - "cpu": { - "type": "string" - }, - "memory": { - "type": "string" - } - } - } - } - }, - "Types.FuryModuleOverrides": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeSelector": { - "$ref": "#/$defs/Types.KubeNodeSelector" - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.KubeToleration" - } - }, - "ingresses": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" - } - } - } - }, - "Types.FuryModuleOverridesIngresses": { - "type": "object", - "additionalProperties": false, - "properties": { - "disableAuth": { - "type": "boolean" - }, - "host": { - "type": "string" - }, - "ingressClass": { - "type": "string" - } - }, - "required": [ - "disableAuth", - "host", - "ingressClass" - ] - } - } -} diff --git a/test/integration/validation-cmd/data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json b/test/integration/validation-cmd/data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json deleted file mode 100644 index 3edea0860..000000000 --- a/test/integration/validation-cmd/data/config-valid-furyctl-yaml/schemas/ekscluster-kfd-v1alpha2.json +++ /dev/null @@ -1,1314 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", - "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", - "type": "object", - "properties": { - "apiVersion": { - "type": "string", - "pattern": "^kfd\\.sighup\\.io/v\\d+((alpha|beta)\\d+)?$" - }, - "kind": { - "type": "string", - "enum": ["EKSCluster"] - }, - "metadata": { - "$ref": "#/$defs/Metadata" - }, - "spec": { - "$ref": "#/$defs/Spec" - } - }, - "additionalProperties": false, - "required": [ - "apiVersion", - "kind", - "metadata", - "spec" - ], - "$defs": { - "Metadata": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ] - }, - "Spec": { - "type": "object", - "additionalProperties": false, - "properties": { - "distributionVersion": { - "$ref": "#/$defs/Types.SemVer" - }, - "tags": { - "$ref": "#/$defs/Types.AwsTags" - }, - "toolsConfiguration": { - "$ref": "#/$defs/Spec.ToolsConfiguration" - }, - "infrastructure": { - "$ref": "#/$defs/Spec.Infrastructure" - }, - "kubernetes": { - "type": "object", - "additionalProperties": true - }, - "distribution": { - "$ref": "#/$defs/Spec.Distribution" - } - }, - "required": [ - "distributionVersion", - "infrastructure", - "kubernetes", - "toolsConfiguration" - ] - }, - - "Spec.ToolsConfiguration": { - "type": "object", - "additionalProperties": false, - "properties": { - "terraform": { - "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform" - } - }, - "required": [ - "terraform" - ] - }, - "Spec.ToolsConfiguration.Terraform": { - "type": "object", - "additionalProperties": false, - "properties": { - "state": { - "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State" - } - }, - "required": [ - "state" - ] - }, - "Spec.ToolsConfiguration.Terraform.State": { - "type": "object", - "additionalProperties": false, - "properties": { - "s3": { - "$ref": "#/$defs/Spec.ToolsConfiguration.Terraform.State.S3" - } - }, - "required": [ - "s3" - ] - }, - "Spec.ToolsConfiguration.Terraform.State.S3": { - "type": "object", - "additionalProperties": false, - "properties": { - "bucketName": { - "type": "string" - }, - "keyPrefix": { - "type": "string" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - } - }, - "required": [ - "bucketName", - "keyPrefix", - "region" - ] - }, - - "Spec.Infrastructure": { - "type": "object", - "additionalProperties": false, - "properties": { - "vpc": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc" - } - }, - "required": [ - "vpc" - ] - }, - "Spec.Infrastructure.Vpc": { - "type": "object", - "additionalProperties": false, - "properties": { - "network": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" - }, - "vpn": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" - } - }, - "required": [ - "network" - ] - }, - "Spec.Infrastructure.Vpc.Network": { - "type": "object", - "additionalProperties": false, - "properties": { - "cidr": { - "$ref": "#/$defs/Types.Cidr" - }, - "subnetsCidrs": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network.SubnetsCidrs" - } - }, - "required": [ - "cidr", - "subnetsCidrs" - ] - }, - "Spec.Infrastructure.Vpc.Network.SubnetsCidrs": { - "type": "object", - "additionalProperties": false, - "properties": { - "private": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - }, - "public": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - } - }, - "required": [ - "private", - "public" - ] - }, - "Spec.Infrastructure.Vpc.Vpn": { - "type": "object", - "additionalProperties": false, - "properties": { - "instances": { - "type": "integer" - }, - "port": { - "$ref": "#/$defs/Types.TcpPort" - }, - "instanceType": { - "type": "string" - }, - "diskSize": { - "type": "integer" - }, - "operatorName": { - "type": "string" - }, - "dhParamsBits": { - "type": "integer" - }, - "vpnClientsSubnetCidr": { - "$ref": "#/$defs/Types.Cidr" - }, - "ssh": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" - } - }, - "required": [ - "dhParamsBits", - "diskSize", - "instances", - "instanceType", - "operatorName", - "port", - "ssh", - "vpnClientsSubnetCidr" - ] - }, - "Spec.Infrastructure.Vpc.Vpn.Ssh": { - "type": "object", - "additionalProperties": false, - "properties": { - "publicKeys": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/$defs/Types.SshPubKey" - }, - { - "$ref": "#/$defs/Types.FileRef" - } - ] - } - }, - "githubUsersName": { - "type": "array", - "items": { - "type": "string" - } - }, - "allowedFromCidrs": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - } - }, - "required": [ - "allowedFromCidrs", - "githubUsersName", - "publicKeys" - ] - }, - - "Spec.Kubernetes": { - "type": "object", - "additionalProperties": false, - "properties": { - "vpcId": { - "$ref": "#/$defs/Types.AwsVpcId" - }, - "subnetIds": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.AwsSubnetId" - } - }, - "apiServerEndpointAccess": { - "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" - }, - "nodeAllowedSshPublicKey": { - "anyOf": [ - { - "$ref": "#/$defs/Types.SshPubKey" - }, - { - "$ref": "#/$defs/Types.FileRef" - } - ] - }, - "nodePools": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool" - } - }, - "awsAuth": { - "$ref": "#/$defs/Spec.Kubernetes.AwsAuth" - } - }, - "required": [ - "apiServerEndpointAccess", - "awsAuth", - "nodeAllowedSshPublicKey", - "nodePools", - "subnetIds", - "vpcId" - ] - }, - "Spec.Kubernetes.APIServerEndpointAccess": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": [ - "public", - "private", - "public_and_private" - ] - }, - "allowedCidrs": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - } - }, - "required": [ - "allowedCidrs", - "type" - ] - }, - "Spec.Kubernetes.NodePool": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "ami": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" - }, - "size": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" - }, - "instance": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.Instance" - }, - "attachedTargetGroups": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "labels": { - "$ref": "#/$defs/Types.KubeLabels" - }, - "taints": { - "$ref": "#/$defs/Types.KubeTaints" - }, - "tags": { - "$ref": "#/$defs/Types.AwsTags" - }, - "additionalFirewallRules": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" - } - } - }, - "required": [ - "additionalFirewallRules", - "ami", - "attachedTargetGroups", - "instance", - "labels", - "name", - "size", - "tags", - "taints" - ] - }, - "Spec.Kubernetes.NodePool.Ami": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "owner": { - "type": "string" - } - }, - "required": [ - "id", - "owner" - ] - }, - "Spec.Kubernetes.NodePool.Instance": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string" - }, - "spot": { - "type": "boolean" - }, - "volumeSize": { - "type": "integer" - } - }, - "required": [ - "spot", - "type", - "volumeSize" - ] - }, - "Spec.Kubernetes.NodePool.Size": { - "type": "object", - "additionalProperties": false, - "properties": { - "min": { - "type": "integer", - "minimum": 1 - }, - "max": { - "type": "integer", - "minimum": 1 - } - }, - "required": [ - "max", - "min" - ] - }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "cidrBlocks": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.Cidr" - } - }, - "protocol": { - "$ref": "#/$defs/Types.AwsIpProtocol" - }, - "ports": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" - } - }, - "required": [ - "cidrBlocks", - "name", - "ports", - "protocol", - "type" - ] - }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { - "type": "object", - "additionalProperties": false, - "properties": { - "from": { - "$ref": "#/$defs/Types.TcpPort" - }, - "to": { - "$ref": "#/$defs/Types.TcpPort" - } - }, - "required": [ - "from", - "to" - ] - }, - "Spec.Kubernetes.AwsAuth": { - "type": "object", - "additionalProperties": false, - "properties": { - "additionalAccounts": { - "type": "array", - "items": { - "type": "string" - } - }, - "users": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.User" - } - }, - "roles": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.AwsAuth.Role" - } - } - }, - "required": [ - "additionalAccounts", - "roles", - "users" - ] - }, - "Spec.Kubernetes.AwsAuth.Role": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string" - }, - "groups": { - "type": "array", - "items": { - "type": "string" - } - }, - "rolearn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "groups", - "rolearn", - "username" - ] - }, - "Spec.Kubernetes.AwsAuth.User": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string" - }, - "groups": { - "type": "array", - "items": { - "type": "string" - } - }, - "userarn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "groups", - "userarn", - "username" - ] - }, - - "Spec.Distribution": { - "type": "object", - "additionalProperties": false, - "properties": { - "common": { - "$ref": "#/$defs/Spec.Distribution.Common" - }, - "modules": { - "$ref": "#/$defs/Spec.Distribution.Modules" - } - }, - "required": [ - "common", - "modules" - ] - }, - "Spec.Distribution.Common": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeSelector": { - "$ref": "#/$defs/Types.KubeNodeSelector" - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.KubeToleration" - } - }, - "provider": { - "$ref": "#/$defs/Spec.Distribution.Common.Provider" - }, - "relativeVendorPath": { - "type": "string" - } - }, - "required": [ - "nodeSelector", - "tolerations", - "provider", - "relativeVendorPath" - ] - }, - "Spec.Distribution.Common.Provider": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string" - } - } - }, - "Spec.Distribution.Modules": { - "type": "object", - "additionalProperties": false, - "properties": { - "auth": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth" - }, - "dr": { - "$ref": "#/$defs/Spec.Distribution.Modules.Dr" - }, - "ingress": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress" - }, - "logging": { - "$ref": "#/$defs/Spec.Distribution.Modules.Logging" - }, - "monitoring": { - "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" - }, - "policy": { - "$ref": "#/$defs/Spec.Distribution.Modules.Policy" - }, - "aws": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws" - } - }, - "required": [ - "auth", - "dr", - "ingress", - "logging", - "monitoring", - "policy" - ] - }, - "Spec.Distribution.Modules.Ingress": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "baseDomain": { - "type": "string" - }, - "nginx": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx" - }, - "certManager": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CERTManager" - }, - "dns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" - } - }, - "required": [ - "baseDomain", - "certManager", - "dns", - "nginx", - "overrides" - ] - }, - "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { - "type": "object", - "additionalProperties": false, - "properties": { - "forecastle": { - "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" - } - } - }, - "Spec.Distribution.Modules.Ingress.Nginx": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": ["single", "dual"] - }, - "tls": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" - } - }, - "required": [ - "tls", - "type" - ] - }, - "Spec.Distribution.Modules.Ingress.Nginx.TLS": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "type": "string", - "enum": ["certManager", "secret", "none"] - }, - "secret": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret" - } - }, - "required": [ - "provider", - "secret" - ] - }, - "Spec.Distribution.Modules.Ingress.Nginx.TLS.Secret": { - "type": "object", - "additionalProperties": false, - "properties": { - "cert": { - "type": "string" - }, - "key": { - "type": "string" - }, - "ca": { - "type": "string" - } - }, - "required": [ - "ca", - "cert", - "key" - ] - }, - "Spec.Distribution.Modules.Ingress.CERTManager": { - "type": "object", - "additionalProperties": false, - "properties": { - "clusterIssuer": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer" - } - }, - "required": [ - "clusterIssuer" - ] - }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" - } - }, - "required": [ - "name", - "type" - ] - }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.DNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "public": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Public" - }, - "private": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" - } - }, - "required": [ - "public", - "private" - ] - }, - "Spec.Distribution.Modules.Ingress.DNS.Public": { - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "create": { - "type": "boolean" - } - }, - "required": [ - "enabled", - "name", - "create" - ] - }, - "Spec.Distribution.Modules.Ingress.DNS.Private": { - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "vpcId": { - "type": "string" - } - }, - "required": [ - "enabled", - "name", - "vpcId" - ] - }, - "Spec.Distribution.Modules.Logging": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "opensearch": { - "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" - } - } - }, - "Spec.Distribution.Modules.Logging.Opensearch": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": ["single", "triple"] - }, - "resources": { - "$ref": "#/$defs/Types.KubeResources" - }, - "storage_request": { - "type": "string" - } - }, - "required": [ - "type" - ] - }, - "Spec.Distribution.Modules.Monitoring": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "prometheus": { - "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Prometheus" - } - } - }, - "Spec.Distribution.Modules.Monitoring.Prometheus": { - "type": "object", - "additionalProperties": false, - "properties": { - "resources": { - "$ref": "#/$defs/Types.KubeResources" - } - } - }, - "Spec.Distribution.Modules.Policy": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "gatekeeper": { - "$ref": "#/$defs/Spec.Distribution.Modules.Policy.Gatekeeper" - } - } - }, - "Spec.Distribution.Modules.Policy.Gatekeeper": { - "type": "object", - "additionalProperties": false, - "properties": { - "additionalExcludedNamespaces": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Spec.Distribution.Modules.Dr": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "velero": { - "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero" - } - } - }, - "Spec.Distribution.Modules.Dr.Velero": { - "type": "object", - "additionalProperties": false, - "properties": { - "eks": { - "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" - } - } - }, - "Spec.Distribution.Modules.Dr.Velero.Eks": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "bucket": { - "type": "string" - } - } - }, - "Spec.Distribution.Modules.Auth": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides" - }, - "provider": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" - }, - "pomerium": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" - }, - "dex": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Dex" - } - } - }, - - "Spec.Distribution.Modules.Auth.Overrides": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeSelector": { - "$ref": "#/$defs/Types.KubeNodeSelector" - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.KubeToleration" - } - }, - "ingresses": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Overrides.Ingresses" - } - } - } - }, - "Spec.Distribution.Modules.Auth.Overrides.Ingresses": { - "type": "object", - "additionalProperties": false, - "properties": { - "host": { - "type": "string" - }, - "ingressClass": { - "type": "string" - } - }, - "required": [ - "host", - "ingressClass" - ] - }, - "Spec.Distribution.Modules.Auth.Provider": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": ["none", "basicAuth", "sso"] - }, - "basicAuth": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider.BasicAuth" - } - }, - "required": [ - "type" - ] - }, - "Spec.Distribution.Modules.Auth.Provider.BasicAuth": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "required": [ - "username", - "password" - ] - }, - "Spec.Distribution.Modules.Auth.Pomerium": { - "type": "object", - "additionalProperties": false, - "properties": { - "secrets": { - "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium.Secrets" - } - }, - "required": [ - "secrets" - ] - }, - "Spec.Distribution.Modules.Auth.Pomerium.Secrets": { - "type": "object", - "additionalProperties": false, - "properties": { - "COOKIE_SECRET": { - "type": "string" - }, - "IDP_CLIENT_SECRET": { - "type": "string" - }, - "SHARED_SECRET": { - "type": "string" - } - }, - "required": [ - "COOKIE_SECRET", - "IDP_CLIENT_SECRET", - "SHARED_SECRET" - ] - }, - "Spec.Distribution.Modules.Auth.Dex": { - "type": "object", - "additionalProperties": false, - "properties": { - "connectors": { - "type": "array" - } - } - }, - "Spec.Distribution.Modules.Aws": { - "type": "object", - "additionalProperties": false, - "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, - "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" - } - } - }, - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeGroupAutoDiscovery": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "iamRoleArn", - "nodeGroupAutoDiscovery" - ] - }, - - "Types.SemVer": { - "type": "string", - "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" - }, - "Types.IpAddress": { - "type": "string", - "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\b){4}$" - }, - "Types.Cidr": { - "type": "string", - "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}\\/(3[0-2]|[1-2][0-9]|[0-9])$" - }, - "Types.FileRef": { - "type": "string", - "pattern": "^\\{file\\:\\/\\/.+\\}$" - }, - "Types.EnvRef": { - "type": "string", - "pattern": "\\{^env\\:\\/\\/.*\\}$" - }, - "Types.TcpPort": { - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "Types.SshPubKey": { - "type": "string", - "pattern": "^ssh\\-(dsa|ecdsa|ecdsa-sk|ed25519|ed25519-sk|rsa)\\s+" - }, - "Types.Uri": { - "type": "string", - "pattern": "^(http|https)\\:\\/\\/.+$" - }, - "Types.AwsArn": { - "type": "string", - "pattern": "^arn:(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" - }, - "Types.AwsRegion": { - "type": "string", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ca-central-1", - "eu-central-1", - "eu-north-1", - "eu-south-1", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2" - ] - }, - "Types.AwsVpcId": { - "type": "string", - "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{16})$" - }, - "Types.AwsSubnetId": { - "type": "string", - "pattern": "^subnet\\-[0-9a-f]{16}$" - }, - "Types.AwsTags": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "Types.AwsIpProtocol": { - "type": "string", - "enum": ["tcp", "udp", "icmp", "icmpv6", "-1"] - }, - "Types.KubeLabels": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "Types.KubeTaints": { - "type": "array", - "items": { - "type": "string", - "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" - } - }, - "Types.KubeNodeSelector": { - "type": "object", - "additionalProperties": { "type": "string" } - }, - "Types.KubeToleration": { - "type": "object", - "additionalProperties": false, - "properties": { - "effect": { - "type": "string", - "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] - }, - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "effect", - "key", - "value" - ] - }, - "Types.KubeResources": { - "type": "object", - "additionalProperties": false, - "properties": { - "requests": { - "type": "object", - "additionalProperties": false, - "properties": { - "cpu": { - "type": "string" - }, - "memory": { - "type": "string" - } - } - }, - "limits": { - "type": "object", - "additionalProperties": false, - "properties": { - "cpu": { - "type": "string" - }, - "memory": { - "type": "string" - } - } - } - } - }, - "Types.FuryModuleOverrides": { - "type": "object", - "additionalProperties": false, - "properties": { - "nodeSelector": { - "$ref": "#/$defs/Types.KubeNodeSelector" - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "#/$defs/Types.KubeToleration" - } - }, - "ingresses": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/Types.FuryModuleOverridesIngresses" - } - } - } - }, - "Types.FuryModuleOverridesIngresses": { - "type": "object", - "additionalProperties": false, - "properties": { - "disableAuth": { - "type": "boolean" - }, - "host": { - "type": "string" - }, - "ingressClass": { - "type": "string" - } - }, - "required": [ - "disableAuth", - "host", - "ingressClass" - ] - } - } -} diff --git a/test/integration/validation-cmd/data/dependencies-correct/ansible b/test/integration/validation-cmd/data/dependencies-correct/ansible deleted file mode 100755 index 7e6d30dae..000000000 --- a/test/integration/validation-cmd/data/dependencies-correct/ansible +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -cat <&3 - fi - [ "${status}" -eq 0 ] -} - -@test "invalid furyctl yaml" { - info - test_dir="./test/integration/validation-cmd/data/config-invalid-furyctl-yaml" - abs_test_dir=${PWD}/${test_dir} - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} - } - run init - - [ "${status}" -eq 1 ] - - if [[ ${output} != *"validation failed"* ]]; then - echo "${output}" >&3 - fi - [[ "${output}" == *"validation failed"* ]] -} - -@test "valid furyctl yaml" { - info - test_dir="./test/integration/validation-cmd/data/config-valid-furyctl-yaml" - abs_test_dir=${PWD}/${test_dir} - init(){ - cd ${test_dir} && ${FURYCTL} -d --debug validate config --config ${abs_test_dir}/furyctl.yaml --distro-location ${abs_test_dir} - } - run init - - [ "${status}" -eq 0 ] -} - -@test "dependencies missing" { - info - test_dir="./test/integration/validation-cmd/data/dependencies-missing" - abs_test_dir=${PWD}/${test_dir} - init(){ - cd ${test_dir} && \ - ${FURYCTL} -d --debug \ - validate dependencies \ - --config ${abs_test_dir}/furyctl.yaml \ - --distro-location ${abs_test_dir} \ - --bin-path=${abs_test_dir} - } - run init - - [ "${status}" -eq 1 ] - - if [[ ${output} != *"ansible: no such file or directory"* ]] || \ - [[ ${output} != *"terraform: no such file or directory"* ]] || \ - [[ ${output} != *"kubectl: no such file or directory"* ]] || \ - [[ ${output} != *"kustomize: no such file or directory"* ]] || \ - [[ ${output} != *"furyagent: no such file or directory"* ]]; then - echo "${output}" >&3 - fi - - [[ "${output}" == *"dependencies are not satisfied"* ]] -} - -@test "wrong dependencies installed" { - info - test_dir="./test/integration/validation-cmd/data/dependencies-wrong" - abs_test_dir=${PWD}/${test_dir} - init(){ - cd ${test_dir} && \ - ${FURYCTL} -d --debug \ - validate dependencies \ - --config ${abs_test_dir}/furyctl.yaml \ - --distro-location ${abs_test_dir} \ - --bin-path=${abs_test_dir} - } - run init - - [ "${status}" -eq 1 ] - - if [[ ${output} != *"ansible version on system"* ]] || \ - [[ ${output} != *"terraform version on system"* ]] || \ - [[ ${output} != *"kubectl version on system"* ]] || \ - [[ ${output} != *"kustomize version on system"* ]] || \ - [[ ${output} != *"furyagent version on system"* ]]; then - echo "${output}" >&3 - fi - - [[ "${output}" == *"dependencies are not satisfied"* ]] -} - -@test "correct dependencies installed" { - info - test_dir="./test/integration/validation-cmd/data/dependencies-correct" - abs_test_dir=${PWD}/${test_dir} - init(){ - export AWS_ACCESS_KEY_ID=foo - export AWS_SECRET_ACCESS_KEY=bar - export AWS_DEFAULT_REGION=baz - - cd ${test_dir} && \ - ${FURYCTL} -d --debug \ - validate dependencies \ - --config ${abs_test_dir}/furyctl.yaml \ - --distro-location ${abs_test_dir} \ - --bin-path=${abs_test_dir} - } - run init - - [ "${status}" -eq 0 ] - - if [[ ${output} != *"ansible version on system"* ]] && \ - [[ ${output} != *"terraform version on system"* ]] && \ - [[ ${output} != *"kubectl version on system"* ]] && \ - [[ ${output} != *"kustomize version on system"* ]] && \ - [[ ${output} != *"furyagent version on system"* ]]; then - echo "${output}" >&3 - fi - - [[ "${output}" == *"Dependencies validation succeeded"* ]] -} From eecb35247974c5d1b637a36c22c58002239c046f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 17 Jan 2023 18:17:26 +0100 Subject: [PATCH 072/383] chore: rename create cluster flag's functions (#161) --- cmd/create/cluster.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index ebcf3846d..4081364c8 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -56,7 +56,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { }, RunE: func(cmd *cobra.Command, _ []string) error { // Get flags. - flags, err := getCmdFlags(cmd, tracker, cmdEvent) + flags, err := getCreateClusterCmdFlags(cmd, tracker, cmdEvent) if err != nil { return err } @@ -206,12 +206,12 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { }, } - setupClusterCmdFlags(cmd) + setupCreateClusterCmdFlags(cmd) return cmd } -func getCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (ClusterCmdFlags, error) { +func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (ClusterCmdFlags, error) { debug, err := cmdutil.BoolFlag(cmd, "debug", tracker, cmdEvent) if err != nil { return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "debug") @@ -297,7 +297,7 @@ func getCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analyt }, nil } -func setupClusterCmdFlags(cmd *cobra.Command) { +func setupCreateClusterCmdFlags(cmd *cobra.Command) { cmd.Flags().StringP( "config", "c", From faa473a2cae52c1bd5a24fe62e272e69bbf0ebbb Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 18 Jan 2023 17:04:14 +0100 Subject: [PATCH 073/383] fix: already exists on furyagent cert creation err handled --- internal/tool/furyagent/runner.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index a6b710f9f..4d0ebd755 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path" + "strings" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" @@ -42,7 +43,9 @@ func (r *Runner) ConfigOpenvpnClient(name string) error { }) if err := cmd.Run(); err != nil { - return fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) + if !strings.Contains(err.Error(), "already exists") { + return fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) + } } err := os.WriteFile(path.Join(r.paths.WorkDir, From 46020020fde1f765749d4b29fcd411f77c58699d Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 19 Jan 2023 10:55:19 +0100 Subject: [PATCH 074/383] feat: improved logging UX for distribution phase --- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index fb71c3a1f..97f9cda48 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -167,7 +167,7 @@ func (d *Distribution) Exec() error { return err } - logrus.Info("Deleting blocking resources...") + logrus.Info("Deleting blocking resources...[PersistentVolumeClaims, StatefulSets, Logging, Prometheus]") if err = d.deleteBlockingResources(); err != nil { return err @@ -266,7 +266,8 @@ func (d *Distribution) deleteIngresses() error { return fmt.Errorf("error deleting ingresses: %w", err) } - logrus.Debugf("waiting for records to be deleted...") + logrus.Debugf("waiting for DNS records to be deleted...[ETA: 4 minutes]") + time.Sleep(dur) return nil From 75f7ab27a55527a596948ead8ce733fc8e9349c4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:19:30 +0100 Subject: [PATCH 075/383] Feat: Warn the user about connectivity when running kubernetes phase (#170) * feat: added message to warn the user about checking cluster connectivity * fix: linting --- internal/apis/kfd/v1alpha2/eks/creator.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 2ec8762e6..c68385603 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -137,6 +137,9 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: + logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + + " (e.g. check VPN connection is active`), otherwise the installation will fail.") + if err = kube.Exec(); err != nil { return fmt.Errorf("error while executing kubernetes phase: %w", err) } From e940d757dc756ee3fe46fd61c7224d96428c1183 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:20:44 +0100 Subject: [PATCH 076/383] Feat: UX improvement about unsupported KFD version (#173) * feat: added info to the user for unsupported KFD version download * fix: changed unsupported KFD version error message * chore: better ux with wrong distribution version --- internal/distribution/download.go | 46 +++++++++++++++++++++++++++---- internal/x/net/hashicorp.go | 4 +-- test/e2e/furyctl_test.go | 2 +- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 7e6433a56..478294f4e 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -19,7 +19,7 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const DefaultBaseURL = "https://git@github.com/sighupio/fury-distribution?ref=%s" +const DefaultBaseURL = "git::git@github.com:sighupio/fury-distribution?ref=%s" var ( ErrChangingFilePermissions = errors.New("error changing file permissions") @@ -33,6 +33,7 @@ var ( ErrWriteFile = errors.New("error writing file") ErrYamlMarshalFile = errors.New("error marshaling yaml file") ErrYamlUnmarshalFile = errors.New("error unmarshaling yaml file") + ErrUnsupportedVersion = errors.New("unsupported KFD version") ) type DownloadResult struct { @@ -69,17 +70,19 @@ func (d *Downloader) DoDownload( distroLocation string, minimalConf config.Furyctl, ) (DownloadResult, error) { + url := distroLocation + if err := d.validate.Struct(minimalConf); err != nil { return DownloadResult{}, fmt.Errorf("invalid furyctl config: %w", err) } if distroLocation == "" { - distroLocation = fmt.Sprintf(DefaultBaseURL, minimalConf.Spec.DistributionVersion) + url = fmt.Sprintf(DefaultBaseURL, minimalConf.Spec.DistributionVersion) } - if strings.HasPrefix(distroLocation, ".") { + if strings.HasPrefix(url, ".") { var err error - if distroLocation, err = filepath.Abs(distroLocation); err != nil { + if url, err = filepath.Abs(url); err != nil { return DownloadResult{}, fmt.Errorf("%w: %v", ErrResolvingAbsPath, err) } } @@ -89,17 +92,50 @@ func (d *Downloader) DoDownload( return DownloadResult{}, fmt.Errorf("%w: %v", ErrCreatingTempDir, err) } - src := distroLocation + src := url dst := filepath.Join(baseDst, "data") logrus.Debugf("Downloading '%s' in '%s'", src, dst) if err := netx.NewGoGetterClient().Download(src, dst); err != nil { + if errors.Is(err, netx.ErrDownloadOptionsExhausted) { + if distroLocation == "" { + return DownloadResult{}, fmt.Errorf("%w: seems like the specified version "+ + "%s does not exist, try another version from the official repository", + ErrUnsupportedVersion, + minimalConf.Spec.DistributionVersion, + ) + } + + return DownloadResult{}, fmt.Errorf("%w: seems like the specified location %s"+ + " does not exist, try another version from the official repository", + ErrUnsupportedVersion, + url, + ) + } + return DownloadResult{}, fmt.Errorf("%w '%s': %v", ErrDownloadingFolder, src, err) } kfdPath := filepath.Join(dst, "kfd.yaml") + _, err = os.Stat(kfdPath) + if os.IsNotExist(err) { + if distroLocation == "" { + return DownloadResult{}, fmt.Errorf("%w: %s is not supported by furyctl-ng, "+ + "try another version or use flag --distro-location to specify a custom location", + ErrUnsupportedVersion, + minimalConf.Spec.DistributionVersion, + ) + } + + return DownloadResult{}, fmt.Errorf("%w: seems like %s is not supported by furyctl-ng, "+ + "try another version from the official repository", + ErrUnsupportedVersion, + distroLocation, + ) + } + kfdManifest, err := yamlx.FromFileV3[config.KFD](kfdPath) if err != nil { return DownloadResult{}, err diff --git a/internal/x/net/hashicorp.go b/internal/x/net/hashicorp.go index 154312c2c..8405827bd 100644 --- a/internal/x/net/hashicorp.go +++ b/internal/x/net/hashicorp.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" ) -var errDownloadOptionsExausted = errors.New("downloading options exausted") +var ErrDownloadOptionsExhausted = errors.New("downloading options exhausted") func NewGoGetterClient() *GoGetterClient { return &GoGetterClient{ @@ -50,7 +50,7 @@ func (g *GoGetterClient) Download(src, dst string) error { logrus.Debug(err) } - return errDownloadOptionsExausted + return ErrDownloadOptionsExhausted } // URLHasForcedProtocol checks if the url has a forced protocol as described in hashicorp/go-getter. diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 9743a8a2d..1e32e8ff7 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -164,7 +164,7 @@ var ( out, err := FuryctlValidateConfig("../data/e2e/validate/config/nodistro") Expect(err).To(HaveOccurred()) - Expect(out).To(ContainSubstring("kfd.yaml: no such file or directory")) + Expect(out).To(ContainSubstring("unsupported KFD version")) }) It("should report an error when config validation fails", func() { From 4eec3152db4fb59cf513730ddc6564200e5029df Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 23 Jan 2023 12:03:26 +0100 Subject: [PATCH 077/383] Feat: support file:// and env:// for every phase in furyctl.yaml (#174) * feat: parsing dynamic values in all phases pt1 * fix: tfvars issues * chore: added unit tests on buffer * fix: mapper unit test * feat: added test to relative path in mapper * chore: upgrade go.mod to newer distribution dep * chore: types consistency Co-authored-by: Claudio Beatrice * fix: fury-distribution dep version Co-authored-by: Claudio Beatrice --- go.mod | 2 +- go.sum | 4 +- .../kfd/v1alpha2/eks/create/infrastructure.go | 99 +++-- .../kfd/v1alpha2/eks/create/kubernetes.go | 368 ++++++++++++------ internal/template/mapper/mapper.go | 60 ++- internal/template/mapper/mapper_test.go | 51 +++ internal/x/bytes/buffer.go | 45 +++ internal/x/bytes/buffer_test.go | 81 ++++ 8 files changed, 525 insertions(+), 185 deletions(-) create mode 100644 internal/x/bytes/buffer.go create mode 100644 internal/x/bytes/buffer_test.go diff --git a/go.mod b/go.mod index 22565c1c7..472e18e7c 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/onsi/ginkgo/v2 v2.6.1 github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 - github.com/sighupio/fury-distribution v1.24.1-0.20230103153037-ad0c881984b3 + github.com/sighupio/fury-distribution v1.24.1-0.20230120183200-c7572efd3b23 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 4175afaaf..bae420529 100644 --- a/go.sum +++ b/go.sum @@ -286,8 +286,8 @@ github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdk github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.24.1-0.20230103153037-ad0c881984b3 h1:ypsKhdTtCEI4+N4MYWCcbm1rcf4gdUb+Yyrc0cqNySk= -github.com/sighupio/fury-distribution v1.24.1-0.20230103153037-ad0c881984b3/go.mod h1:MpW/zGMlwJIQoBgZ6xWwL6RN6j7N5bCEX8949p3Pfik= +github.com/sighupio/fury-distribution v1.24.1-0.20230120183200-c7572efd3b23 h1:x0USah6e/HTe5QNyp/mqFdFS67LLP2nNYY9jTLxhq54= +github.com/sighupio/fury-distribution v1.24.1-0.20230120183200-c7572efd3b23/go.mod h1:MpW/zGMlwJIQoBgZ6xWwL6RN6j7N5bCEX8949p3Pfik= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 33b5a3d6c..4d09171dd 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -26,6 +26,7 @@ import ( "github.com/sighupio/furyctl/internal/tool/furyagent" "github.com/sighupio/furyctl/internal/tool/openvpn" "github.com/sighupio/furyctl/internal/tool/terraform" + bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" ) @@ -272,15 +273,16 @@ func (i *Infrastructure) copyOpenvpnToWorkDir() error { func (i *Infrastructure) createTfVars() error { var buffer bytes.Buffer - _, err := buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", i.furyctlConf.Metadata.Name)) + err := bytesx.SafeWriteToBuffer(&buffer, "name = \"%v\"\n", i.furyctlConf.Metadata.Name) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf( + err = bytesx.SafeWriteToBuffer( + &buffer, "network_cidr = \"%v\"\n", i.furyctlConf.Spec.Infrastructure.Vpc.Network.Cidr, - )) + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -297,16 +299,18 @@ func (i *Infrastructure) createTfVars() error { privateSubnetworkCidrs[i] = fmt.Sprintf("\"%v\"", cidr) } - _, err = buffer.WriteString(fmt.Sprintf( + err = bytesx.SafeWriteToBuffer(&buffer, "public_subnetwork_cidrs = [%v]\n", - strings.Join(publicSubnetworkCidrs, ","))) + strings.Join(publicSubnetworkCidrs, ","), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf( + err = bytesx.SafeWriteToBuffer(&buffer, "private_subnetwork_cidrs = [%v]\n", - strings.Join(privateSubnetworkCidrs, ","))) + strings.Join(privateSubnetworkCidrs, ","), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -322,22 +326,20 @@ func (i *Infrastructure) createTfVars() error { } func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { - _, err := buffer.WriteString( - fmt.Sprintf( - "vpn_subnetwork_cidr = \"%v\"\n", - i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.VpnClientsSubnetCidr, - ), + err := bytesx.SafeWriteToBuffer( + buffer, + "vpn_subnetwork_cidr = \"%v\"\n", + i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.VpnClientsSubnetCidr, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances != nil { - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_instances = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances, - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_instances = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -345,11 +347,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { } if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port != 0 { - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_port = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port, - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_port = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -358,11 +359,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType != "" { - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_instance_type = \"%v\"\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType, - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_instance_type = \"%v\"\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -371,11 +371,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize != 0 { - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_instance_disk_size = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize, - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_instance_disk_size = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -384,11 +383,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName != "" { - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_operator_name = \"%v\"\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName, - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_operator_name = \"%v\"\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -397,11 +395,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits != 0 { - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_dhparams_bits = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits, - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_dhparams_bits = %v\n", + *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -415,11 +412,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { allowedCidrs[i] = fmt.Sprintf("\"%v\"", cidr) } - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_operator_cidrs = [%v]\n", - strings.Join(allowedCidrs, ","), - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_operator_cidrs = [%v]\n", + strings.Join(allowedCidrs, ","), ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -433,11 +429,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { githubUsers[i] = fmt.Sprintf("\"%v\"", gu) } - _, err = buffer.WriteString( - fmt.Sprintf( - "vpn_ssh_users = [%v]\n", - strings.Join(githubUsers, ","), - ), + err = bytesx.SafeWriteToBuffer( + buffer, + "vpn_ssh_users = [%v]\n", + strings.Join(githubUsers, ","), ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 6430a4830..6bdb57624 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -24,6 +24,7 @@ import ( "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/terraform" + bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" kubex "github.com/sighupio/furyctl/internal/x/kube" @@ -201,7 +202,7 @@ func (k *Kubernetes) copyFromTemplate() error { return nil } -//nolint:gocyclo,maintidx,gocognit,funlen,revive,cyclop // it will be refactored +//nolint:gocyclo,maintidx,funlen // it will be refactored func (k *Kubernetes) createTfVars() error { var buffer bytes.Buffer @@ -263,12 +264,20 @@ func (k *Kubernetes) createTfVars() error { } } - _, err := buffer.WriteString(fmt.Sprintf("cluster_name = \"%v\"\n", k.furyctlConf.Metadata.Name)) + err := bytesx.SafeWriteToBuffer( + &buffer, + "cluster_name = \"%v\"\n", + k.furyctlConf.Metadata.Name, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("cluster_version = \"%v\"\n", k.kfdManifest.Kubernetes.Eks.Version)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_version = \"%v\"\n", + k.kfdManifest.Kubernetes.Eks.Version, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -281,7 +290,11 @@ func (k *Kubernetes) createTfVars() error { vpcIDSource = new(schema.TypesAwsVpcId) } - _, err = buffer.WriteString(fmt.Sprintf("network = \"%v\"\n", *vpcIDSource)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "network = \"%v\"\n", + *vpcIDSource, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -292,7 +305,11 @@ func (k *Kubernetes) createTfVars() error { subnetIds[i] = fmt.Sprintf("\"%v\"", subnetID) } - _, err = buffer.WriteString(fmt.Sprintf("subnetworks = [%v]\n", strings.Join(subnetIds, ","))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "subnetworks = [%v]\n", + strings.Join(subnetIds, ","), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -303,13 +320,19 @@ func (k *Kubernetes) createTfVars() error { dmzCidrRange[i] = fmt.Sprintf("\"%v\"", cidr) } - _, err = buffer.WriteString(fmt.Sprintf("dmz_cidr_range = [%v]\n", strings.Join(dmzCidrRange, ","))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "dmz_cidr_range = [%v]\n", + strings.Join(dmzCidrRange, ","), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString( - fmt.Sprintf("ssh_public_key = \"%v\"\n", k.furyctlConf.Spec.Kubernetes.NodeAllowedSshPublicKey), + err = bytesx.SafeWriteToBuffer( + &buffer, + "ssh_public_key = \"%v\"\n", + k.furyctlConf.Spec.Kubernetes.NodeAllowedSshPublicKey, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -323,7 +346,11 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "tags = %v\n", + string(tags), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -331,11 +358,10 @@ func (k *Kubernetes) createTfVars() error { if k.furyctlConf.Spec.Kubernetes.AwsAuth != nil { if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts) > 0 { - _, err = buffer.WriteString( - fmt.Sprintf( - "eks_map_accounts = [\"%v\"]\n", - strings.Join(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts, "\",\""), - ), + err = bytesx.SafeWriteToBuffer( + &buffer, + "eks_map_accounts = [\"%v\"]\n", + strings.Join(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts, "\",\""), ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -343,56 +369,74 @@ func (k *Kubernetes) createTfVars() error { } if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users) > 0 { - _, err = buffer.WriteString("eks_map_users = [\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "eks_map_users = [\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - for _, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Users { - _, err = buffer.WriteString( - fmt.Sprintf( - `{ - groups = ["%v"] - username = "%v" - userarn = "%v" - },`, - strings.Join(account.Groups, "\",\""), account.Username, account.Userarn, - ), + for i, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Users { + content := "{\ngroups = [\"%v\"]\nusername = \"%v\"\nuserarn = \"%v\"}" + + if i < len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users)-1 { + content += "," + } + + err = bytesx.SafeWriteToBuffer( + &buffer, + content, + strings.Join(account.Groups, "\",\""), + account.Username, + account.Userarn, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - _, err = buffer.WriteString("]\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "]\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles) > 0 { - _, err = buffer.WriteString("eks_map_roles = [\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "eks_map_roles = [\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - for _, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles { - _, err = buffer.WriteString( - fmt.Sprintf( - `{ - groups = ["%v"] - username = "%v" - rolearn = "%v" - },`, - strings.Join(account.Groups, "\",\""), account.Username, account.Rolearn, - ), + for i, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles { + content := "{\ngroups = [\"%v\"]\nusername = \"%v\"\nrolearn = \"%v\"}" + + if i < len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles)-1 { + content += "," + } + + err = bytesx.SafeWriteToBuffer( + &buffer, + content, + strings.Join(account.Groups, "\",\""), + account.Username, + account.Rolearn, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - _, err = buffer.WriteString("]\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "]\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -400,23 +444,36 @@ func (k *Kubernetes) createTfVars() error { } if len(k.furyctlConf.Spec.Kubernetes.NodePools) > 0 { - _, err = buffer.WriteString("node_pools = [\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "node_pools = [\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } for _, np := range k.furyctlConf.Spec.Kubernetes.NodePools { - _, err = buffer.WriteString("{\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "{\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("name = \"%v\"\n", np.Name)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "name = \"%v\"\n", + np.Name, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString("version = null\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "version = null\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -427,22 +484,38 @@ func (k *Kubernetes) createTfVars() error { spot = strconv.FormatBool(*np.Instance.Spot) } - _, err = buffer.WriteString(fmt.Sprintf("spot_instance = %v\n", spot)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "spot_instance = %v\n", + spot, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("min_size = %v\n", np.Size.Min)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "min_size = %v\n", + np.Size.Min, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("max_size = %v\n", np.Size.Max)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "max_size = %v\n", + np.Size.Max, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("instance_type = \"%v\"\n", np.Instance.Type)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "instance_type = \"%v\"\n", + np.Instance.Type, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -454,11 +527,11 @@ func (k *Kubernetes) createTfVars() error { attachedTargetGroups[i] = fmt.Sprintf("\"%v\"", tg) } - _, err = buffer.WriteString( - fmt.Sprintf( - "eks_target_group_arns = [%v]\n", - strings.Join(attachedTargetGroups, ","), - )) + err = bytesx.SafeWriteToBuffer( + &buffer, + "eks_target_group_arns = [%v]\n", + strings.Join(attachedTargetGroups, ","), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -470,63 +543,18 @@ func (k *Kubernetes) createTfVars() error { volumeSize = *np.Instance.VolumeSize } - _, err = buffer.WriteString(fmt.Sprintf("volume_size = %v\n", volumeSize)) + err = bytesx.SafeWriteToBuffer( + &buffer, + "volume_size = %v\n", + volumeSize, + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - if len(np.AdditionalFirewallRules) > 0 { - _, err = buffer.WriteString("additional_firewall_rules = [\n") - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - for _, fwRule := range np.AdditionalFirewallRules { - fwRuleTags := "{}" - - if len(fwRule.Tags) > 0 { - var tags []byte - - tags, err := json.Marshal(fwRule.Tags) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - fwRuleTags = string(tags) - } - - _, err = buffer.WriteString( - fmt.Sprintf( - `{ - name = "%v" - direction = "%v" - cidr_block = "%v" - protocol = "%v" - ports = "%v" - tags = %v - },`, - fwRule.Name, - fwRule.Type, - fwRule.CidrBlocks, - fwRule.Protocol, - fwRule.Ports, - fwRuleTags, - ), - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - - _, err = buffer.WriteString("]\n") - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } else { - _, err = buffer.WriteString("additional_firewall_rules = []\n") - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } + err = k.addFirewallRulesToNodePool(&buffer, np) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } if len(np.SubnetIds) > 0 { @@ -536,12 +564,19 @@ func (k *Kubernetes) createTfVars() error { npSubNetIds[i] = fmt.Sprintf("\"%v\"", subnetID) } - _, err = buffer.WriteString(fmt.Sprintf("subnetworks = [%v]\n", strings.Join(npSubNetIds, ","))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "subnetworks = [%v]\n", + strings.Join(npSubNetIds, ","), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } else { - _, err = buffer.WriteString("subnetworks = null\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "subnetworks = null\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -555,24 +590,38 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("labels = %v\n", string(labels))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "labels = %v\n", + string(labels), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } else { - _, err = buffer.WriteString("labels = {}\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "labels = {}\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } if len(np.Taints) > 0 { - _, err = buffer.WriteString(fmt.Sprintf("taints = [\"%v\"]\n", strings.Join(np.Taints, "\",\""))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "taints = [\"%v\"]\n", + strings.Join(np.Taints, "\",\""), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } else { - _, err = buffer.WriteString("taints = []\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "taints = []\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -586,24 +635,37 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - _, err = buffer.WriteString(fmt.Sprintf("tags = %v\n", string(tags))) + err = bytesx.SafeWriteToBuffer( + &buffer, + "tags = %v\n", + string(tags), + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } else { - _, err = buffer.WriteString("tags = {}\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "tags = {}\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - _, err = buffer.WriteString("},\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "},\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - _, err = buffer.WriteString("]\n") + err = bytesx.SafeWriteToBuffer( + &buffer, + "]\n", + ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -618,3 +680,81 @@ func (k *Kubernetes) createTfVars() error { return nil } + +func (*Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np schema.SpecKubernetesNodePool) error { + if len(np.AdditionalFirewallRules) > 0 { + err := bytesx.SafeWriteToBuffer( + buffer, + "additional_firewall_rules = [\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for i, fwRule := range np.AdditionalFirewallRules { + fwRuleTags := "{}" + + if len(fwRule.Tags) > 0 { + var tags []byte + + tags, err := json.Marshal(fwRule.Tags) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + fwRuleTags = string(tags) + } + + content := "{\nname = \"%v\"\ndirection = \"%v\"\ncidr_block = %v\nprotocol = \"%v\"\n" + + "ports = \"%v\"\ntags = %v\n}" + + if i < len(np.AdditionalFirewallRules)-1 { + content += "," + } + + dmzCidrRanges := make([]string, len(fwRule.CidrBlocks)) + + for i, cidr := range fwRule.CidrBlocks { + dmzCidrRanges[i] = fmt.Sprintf("\"%v\"", cidr) + } + + cidrRange := "" + + if len(dmzCidrRanges) > 0 { + cidrRange = dmzCidrRanges[0] + } + + err = bytesx.SafeWriteToBuffer( + buffer, + content, + fwRule.Name, + fwRule.Type, + cidrRange, + fwRule.Protocol, + fmt.Sprintf("%v-%v", fwRule.Ports.From, fwRule.Ports.To), + fwRuleTags, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + err = bytesx.SafeWriteToBuffer( + buffer, + "]\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + err := bytesx.SafeWriteToBuffer( + buffer, + "additional_firewall_rules = []\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + return nil +} diff --git a/internal/template/mapper/mapper.go b/internal/template/mapper/mapper.go index 2ce166a9d..86edd1fa7 100644 --- a/internal/template/mapper/mapper.go +++ b/internal/template/mapper/mapper.go @@ -16,7 +16,10 @@ const ( File = "file" ) -var errKeyIsNotAString = errors.New("key is not a string") +var ( + errKeyIsNotAString = errors.New("key is not a string") + errUnknownKey = errors.New("unknown key") +) type Mapper struct { context map[string]map[any]any @@ -66,23 +69,13 @@ func injectDynamicRes( spl := strings.Split(key, "://") if len(spl) > 1 { - source := spl[0] - sourceValue := spl[1] - - switch source { - case Env: - envVar := os.Getenv(sourceValue) - parent[parentKey] = envVar - - case File: - content, err := readValueFromFile(sourceValue) - if err != nil { - return nil, err - } - - parent[parentKey] = content + val, err := ParseDynamicValue(k) + if err != nil { + return nil, err } + parent[parentKey] = val + continue } @@ -117,3 +110,38 @@ func readValueFromFile(path string) (string, error) { return string(val), err } + +func ParseDynamicValue(val any) (string, error) { + strVal := fmt.Sprintf("%v", val) + + spl := strings.Split(strVal, "://") + + if len(spl) > 1 { + source := strings.TrimPrefix(spl[0], "{") + sourceValue := strings.TrimSuffix(spl[1], "}") + + switch source { + case Env: + envVar := os.Getenv(sourceValue) + + envVar = strings.TrimRight(envVar, "\n") + + return envVar, nil + + case File: + content, err := readValueFromFile(sourceValue) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + content = strings.TrimRight(content, "\n") + + return content, nil + + default: + return "", fmt.Errorf("%w %s", errUnknownKey, source) + } + } + + return strVal, nil +} diff --git a/internal/template/mapper/mapper_test.go b/internal/template/mapper/mapper_test.go index 54429defc..bddde9c2d 100644 --- a/internal/template/mapper/mapper_test.go +++ b/internal/template/mapper/mapper_test.go @@ -9,7 +9,9 @@ package mapper_test import ( "fmt" "os" + "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" @@ -100,3 +102,52 @@ func TestMapper_MapDynamicValues(t *testing.T) { assert.Equal(t, exampleStr, mapMeta["value"]) } + +func TestMapper_MapDynamicValues_RelativePath(t *testing.T) { + path := "../.." + + timestamp := time.Now().Unix() + + fileName := fmt.Sprintf("test_file-%v.txt", timestamp) + + filePath := filepath.Join(path, fileName) + + exampleStr := "test!" + + err := os.WriteFile(filePath, []byte(exampleStr), os.ModePerm) + + defer os.RemoveAll(filePath) + + assert.NoError(t, err) + + dummyContext := map[string]map[any]any{ + "data": { + "meta": map[any]any{ + "name": map[any]any{"env://TEST_MAPPER_DYNAMIC_VALUE": ""}, + "value": map[any]any{fmt.Sprintf("file://%s", filePath): ""}, + }, + }, + } + + m := mapper.NewMapper(dummyContext) + + err = os.Setenv("TEST_MAPPER_DYNAMIC_VALUE", "test") + + assert.NoError(t, err) + + defer os.Setenv("TEST_MAPPER_DYNAMIC_VALUE", "") + + filledContext, err := m.MapDynamicValues() + + assert.NoError(t, err) + + meta := filledContext["data"]["meta"] + + mapMeta, ok := meta.(map[any]any) + + assert.True(t, ok) + + assert.Equal(t, "test", mapMeta["name"]) + + assert.Equal(t, exampleStr, mapMeta["value"]) +} diff --git a/internal/x/bytes/buffer.go b/internal/x/bytes/buffer.go new file mode 100644 index 000000000..536674f5c --- /dev/null +++ b/internal/x/bytes/buffer.go @@ -0,0 +1,45 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bytesx + +import ( + "bytes" + "fmt" + + "github.com/sighupio/furyctl/internal/template/mapper" +) + +func SafeWriteToBuffer(buffer *bytes.Buffer, content string, values ...any) error { + vs := make([]any, 0) + + for _, sv := range values { + if sv == nil { + continue + } + + v, err := mapper.ParseDynamicValue(sv) + if err != nil { + return fmt.Errorf("error parsing dynamic value: %w", err) + } + + vs = append(vs, fmt.Sprintf("%v", v)) + } + + if len(vs) == 0 { + _, err := buffer.WriteString(content) + if err != nil { + return fmt.Errorf("error writing to buffer: %w", err) + } + + return nil + } + + _, err := buffer.WriteString(fmt.Sprintf(content, vs...)) + if err != nil { + return fmt.Errorf("error writing to buffer: %w", err) + } + + return nil +} diff --git a/internal/x/bytes/buffer_test.go b/internal/x/bytes/buffer_test.go new file mode 100644 index 000000000..0566d50a6 --- /dev/null +++ b/internal/x/bytes/buffer_test.go @@ -0,0 +1,81 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bytesx_test + +import ( + "bytes" + "testing" + + bytesx "github.com/sighupio/furyctl/internal/x/bytes" +) + +func TestSafeWriteToBuffer(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() ([]any, string) + wantStr string + wantErr bool + }{ + { + "empty string value", + func() ([]any, string) { + var values []any + + values = append(values, "") + + return values, "test: %v" + }, + "test: ", + false, + }, + { + "empty value", + func() ([]any, string) { + var values []any + + values = append(values, nil) + + return values, "test" + }, + "test", + false, + }, + { + "empty values", + func() ([]any, string) { + var values []any + + return values, "test" + }, + "test", + false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + var err error + + buffer := bytes.NewBufferString("") + + values, content := tc.setup() + + err = bytesx.SafeWriteToBuffer(buffer, content, values...) + + if !tc.wantErr && err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tc.wantStr != buffer.String() { + t.Fatalf("want %s got %v", tc.wantStr, buffer.String()) + } + }) + } +} From e1203d0eb7b958c7686b8687c4682c3cbb3bac17 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 24 Jan 2023 10:59:01 +0100 Subject: [PATCH 078/383] fix: auto-complete not working due to logrus --- cmd/completion.go | 2 ++ cmd/root.go | 23 ++++++++++++++++++++++- main.go | 5 +---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cmd/completion.go b/cmd/completion.go index 38903092b..d9331bf95 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -9,6 +9,7 @@ import ( "fmt" "os" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/sighupio/furyctl/internal/analytics" @@ -72,6 +73,7 @@ func NewCompletionCmd(tracker *analytics.Tracker) *cobra.Command { Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + logrus.SetLevel(logrus.FatalLevel) }, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/root.go b/cmd/root.go index 4c0faa811..599d8b886 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -43,7 +43,12 @@ const ( spinnerStyle = 11 ) -func NewRootCommand(versions map[string]string, logFile *os.File, tracker *analytics.Tracker) *RootCommand { +func NewRootCommand( + versions map[string]string, + logFile *os.File, + tracker *analytics.Tracker, + token string, +) *RootCommand { // Update channels. r := make(chan app.Release, 1) e := make(chan error, 1) @@ -66,6 +71,18 @@ Furyctl is a simple CLI tool to: PersistentPreRun: func(cmd *cobra.Command, _ []string) { var err error + if cmd.Name() == "__complete" { + old := cmd.PreRun + + cmd.PreRun = func(cmd *cobra.Command, args []string) { + if old != nil { + old(cmd, args) + } + + logrus.SetLevel(logrus.FatalLevel) + } + } + // Async check for updates. go checkUpdates(versions["version"], r, e) // Configure the spinner. @@ -128,6 +145,10 @@ Furyctl is a simple CLI tool to: logrus.Debugf("Changed working directory to %s", absWorkdir) } + + if token == "" { + logrus.Debug("FURYCTL_MIXPANEL_TOKEN is not set") + } }, PersistentPostRun: func(_ *cobra.Command, _ []string) { // Show update message if available at the end of the command. diff --git a/main.go b/main.go index d1841550f..ed374cd67 100644 --- a/main.go +++ b/main.go @@ -66,16 +66,13 @@ func exec() int { } t := os.Getenv("FURYCTL_MIXPANEL_TOKEN") - if t == "" { - log.Debug("FURYCTL_MIXPANEL_TOKEN is not set") - } // Create the analytics tracker. a := analytics.NewTracker(t, versions[version], osArch, runtime.GOOS, "SIGHUP", h) defer a.Flush() - if _, err := cmd.NewRootCommand(versions, logFile, a).ExecuteC(); err != nil { + if _, err := cmd.NewRootCommand(versions, logFile, a, t).ExecuteC(); err != nil { log.Error(err) return 1 From 9d8e13615044ee777cd2efd469767cc7b909df9e Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 24 Jan 2023 11:02:25 +0100 Subject: [PATCH 079/383] refactor: rename old func --- cmd/root.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 599d8b886..247007e72 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -72,11 +72,11 @@ Furyctl is a simple CLI tool to: var err error if cmd.Name() == "__complete" { - old := cmd.PreRun + oldPreRunFunc := cmd.PreRun cmd.PreRun = func(cmd *cobra.Command, args []string) { - if old != nil { - old(cmd, args) + if oldPreRunFunc != nil { + oldPreRunFunc(cmd, args) } logrus.SetLevel(logrus.FatalLevel) From 5732ce2024a20e2199b4464201dcd6b0a6336c00 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 19 Jan 2023 18:25:54 +0100 Subject: [PATCH 080/383] feat: clean up vendor folder before download + remove .git subfolder after dl --- internal/dependencies/download.go | 32 +++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index b86ee7ab1..6c2996002 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -87,6 +87,15 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { errs := []error{} retries := map[string]int{} + dst := filepath.Join(dd.basePath, "vendor", "modules", name) + + if err := iox.CheckDirIsEmpty(dst); err != nil { + err = os.RemoveAll(dst) + if err != nil { + return fmt.Errorf("error removing folder: %w", err) + } + } + for _, prefix := range []string{oldPrefix, newPrefix} { src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s.git?ref=%s", prefix, name, version) @@ -118,7 +127,7 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { continue } - if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "modules", name)); err != nil { + if err := dd.client.Download(src, dst); err != nil { errs = append(errs, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err)) continue @@ -132,6 +141,11 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { if len(errs) > 0 { return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, errs) } + + err := os.RemoveAll(filepath.Join(dst, ".git")) + if err != nil { + return fmt.Errorf("error removing .git subfolder: %w", err) + } } return nil @@ -143,18 +157,32 @@ func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes) error for i := 0; i < insts.NumField(); i++ { name := strings.ToLower(insts.Type().Field(i).Name) + dst := filepath.Join(dd.basePath, "vendor", "installers", name) + v, ok := insts.Field(i).Interface().(config.KFDProvider) if !ok { return fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) } + if err := iox.CheckDirIsEmpty(dst); err != nil { + err = os.RemoveAll(dst) + if err != nil { + return fmt.Errorf("error removing folder: %w", err) + } + } + version := v.Installer src := fmt.Sprintf("git::git@github.com:sighupio/fury-%s-installer?ref=%s", name, version) - if err := dd.client.Download(src, filepath.Join(dd.basePath, "vendor", "installers", name)); err != nil { + if err := dd.client.Download(src, dst); err != nil { return fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err) } + + err := os.RemoveAll(filepath.Join(dst, ".git")) + if err != nil { + return fmt.Errorf("error removing .git subfolder: %w", err) + } } return nil From 10376b1d55c2dbf7b11a3e9537f3f3231461a9d5 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 17:07:15 +0100 Subject: [PATCH 081/383] feat: removing vendor folder --- internal/dependencies/download.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 6c2996002..3aaf0a82b 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -14,6 +14,8 @@ import ( "reflect" "strings" + "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/furyctl/internal/dependencies/tools" "github.com/sighupio/furyctl/internal/distribution" @@ -50,6 +52,20 @@ type Downloader struct { func (dd *Downloader) DownloadAll(kfd config.KFD) ([]error, []string) { errs := []error{} + + vendorFolder := filepath.Join(dd.basePath, "vendor") + + logrus.Debug("Cleaning vendor folder") + + if err := iox.CheckDirIsEmpty(vendorFolder); err != nil { + err = os.RemoveAll(vendorFolder) + if err != nil { + logrus.Debugf("Error while cleaning vendor folder: %v", err) + + return []error{fmt.Errorf("error removing folder: %w", err)}, nil + } + } + if err := dd.DownloadModules(kfd.Modules); err != nil { errs = append(errs, err) } @@ -89,13 +105,6 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { dst := filepath.Join(dd.basePath, "vendor", "modules", name) - if err := iox.CheckDirIsEmpty(dst); err != nil { - err = os.RemoveAll(dst) - if err != nil { - return fmt.Errorf("error removing folder: %w", err) - } - } - for _, prefix := range []string{oldPrefix, newPrefix} { src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s.git?ref=%s", prefix, name, version) @@ -164,13 +173,6 @@ func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes) error return fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) } - if err := iox.CheckDirIsEmpty(dst); err != nil { - err = os.RemoveAll(dst) - if err != nil { - return fmt.Errorf("error removing folder: %w", err) - } - } - version := v.Installer src := fmt.Sprintf("git::git@github.com:sighupio/fury-%s-installer?ref=%s", name, version) From 57468eee17b78656d3cdf4d11016ea6bb18ebadc Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 20 Jan 2023 11:53:10 +0100 Subject: [PATCH 082/383] feat: added depth=1 to go-getter urls --- internal/dependencies/download.go | 4 ++-- internal/distribution/download.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 3aaf0a82b..2c668521c 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -106,7 +106,7 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { dst := filepath.Join(dd.basePath, "vendor", "modules", name) for _, prefix := range []string{oldPrefix, newPrefix} { - src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s.git?ref=%s", prefix, name, version) + src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s.git?ref=%s&depth=1", prefix, name, version) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, createURL(prefix, name, version), nil) if err != nil { @@ -175,7 +175,7 @@ func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes) error version := v.Installer - src := fmt.Sprintf("git::git@github.com:sighupio/fury-%s-installer?ref=%s", name, version) + src := fmt.Sprintf("git::git@github.com:sighupio/fury-%s-installer?ref=%s&depth=1", name, version) if err := dd.client.Download(src, dst); err != nil { return fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err) diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 478294f4e..5cdad811b 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -19,7 +19,7 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const DefaultBaseURL = "git::git@github.com:sighupio/fury-distribution?ref=%s" +const DefaultBaseURL = "git::git@github.com:sighupio/fury-distribution?ref=%s&depth=1" var ( ErrChangingFilePermissions = errors.New("error changing file permissions") From a268ce20da15544bb521f0cebe38816b6ccf2919 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 20 Jan 2023 11:57:59 +0100 Subject: [PATCH 083/383] chore: added also to installer and removed non necessary .git --- internal/dependencies/download.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 2c668521c..5e5a4b379 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -106,7 +106,7 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { dst := filepath.Join(dd.basePath, "vendor", "modules", name) for _, prefix := range []string{oldPrefix, newPrefix} { - src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s.git?ref=%s&depth=1", prefix, name, version) + src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s?ref=%s&depth=1", prefix, name, version) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, createURL(prefix, name, version), nil) if err != nil { From 418e9fee62b7c2a5dbc9650a1926820160fe0a78 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 20 Jan 2023 19:39:31 +0100 Subject: [PATCH 084/383] chore: added depth=1 to --distro-location help message --- cmd/create/cluster.go | 2 +- cmd/create/config.go | 2 +- cmd/delete/cluster.go | 2 +- cmd/download/dependencies.go | 2 +- cmd/validate/config.go | 2 +- cmd/validate/dependencies.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 4081364c8..384f26a9e 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -327,7 +327,7 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "", "Location where to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME). "+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1). "+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/create/config.go b/cmd/create/config.go index 3c182460f..1cba67c9d 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -140,7 +140,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index e5526ca25..517775cf3 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -203,7 +203,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 606db3b81..04b34cdb9 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -130,7 +130,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index b21afdaec..c72886d65 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -93,7 +93,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 170488863..3969b7fa0 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -133,7 +133,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ "Any format supported by hashicorp/go-getter can be used.", ) From 15d70bd86aa60e07b6a675cef5efbb80eb3b5c19 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 19 Jan 2023 18:06:01 +0100 Subject: [PATCH 085/383] feat: added cluster connectivity check in distribution phase --- .../apis/kfd/v1alpha2/eks/create/distribution.go | 7 +++++++ internal/tool/kubectl/runner.go | 12 +++++++++++- internal/tool/runner.go | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 75ccab5d7..cf0105ac7 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -42,6 +42,7 @@ var ( errCastingDNSPubIamToStr = errors.New("error casting external_dns_public_iam_role_arn output to string") errCastingCertIamToStr = errors.New("error casting cert_manager_iam_role_arn output to string") errCastingVelIamToStr = errors.New("error casting velero_iam_role_arn output to string") + errClusterConnect = errors.New("error connecting to cluster") ) type Distribution struct { @@ -201,6 +202,12 @@ func (d *Distribution) Exec() error { return err } + logrus.Info("Checking cluster connectivity...") + + if _, err := d.kubeRunner.Version(); err != nil { + return errClusterConnect + } + logrus.Info("Building manifests...") manifestsOutPath, err := d.buildManifests() diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index e510160ea..0d36b149a 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -152,8 +152,18 @@ func (r *Runner) Delete(manifestPath string, params ...string) error { } func (r *Runner) Version() (string, error) { + args := []string{"version"} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + if !r.serverSide { + args = append(args, "--client") + } + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: []string{"version", "--client"}, + Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, })) diff --git a/internal/tool/runner.go b/internal/tool/runner.go index c3cb45fcc..5b695902e 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -75,7 +75,7 @@ func (rf *RunnerFactory) Create(name, version, workDir string) Runner { Kubectl: filepath.Join(rf.paths.Bin, name, version, name), WorkDir: workDir, }, - true, true) + false, true) } if name == Kustomize { From 3cf11eb9a9da696bb70ee544720688572d5011e4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 19 Jan 2023 19:31:42 +0100 Subject: [PATCH 086/383] feat: introduce new parameter into kubectl runner for client version --- .../kfd/v1alpha2/eks/create/distribution.go | 1 + internal/apis/kfd/v1alpha2/eks/creator.go | 2 +- .../kfd/v1alpha2/eks/delete/distribution.go | 1 + internal/dependencies/tools/kubectl_test.go | 2 +- internal/tool/kubectl/runner.go | 22 ++++++++++--------- internal/tool/kubectl/runner_test.go | 2 +- internal/tool/runner.go | 2 +- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index cf0105ac7..8078a8622 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -109,6 +109,7 @@ func NewDistribution( }, true, true, + false, ), dryRun: dryRun, }, nil diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index c68385603..8080f7667 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -223,7 +223,7 @@ func (v *ClusterCreator) storeClusterConfig() error { Kubectl: path.Join(v.paths.BinPath, "kubectl", v.kfdManifest.Tools.Common.Kubectl.Version, "kubectl"), WorkDir: v.paths.WorkDir, Kubeconfig: v.paths.Kubeconfig, - }, true, true) + }, true, true, false) logrus.Info("Storing cluster config...") diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 97f9cda48..b2082ee4f 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -85,6 +85,7 @@ func NewDistribution( }, true, true, + false, ), dryRun: dryRun, }, nil diff --git a/internal/dependencies/tools/kubectl_test.go b/internal/dependencies/tools/kubectl_test.go index edff34e04..119b3e8b8 100644 --- a/internal/dependencies/tools/kubectl_test.go +++ b/internal/dependencies/tools/kubectl_test.go @@ -127,5 +127,5 @@ func Test_Kubectl_CheckBinVersion(t *testing.T) { func newKubectlRunner() *kubectl.Runner { return kubectl.NewRunner(execx.NewFakeExecutor(), kubectl.Paths{ Kubectl: "kubectl", - }, true, true) + }, true, true, true) } diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 0d36b149a..dc047558a 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -22,18 +22,20 @@ type Paths struct { } type Runner struct { - executor execx.Executor - paths Paths - serverSide bool - skipNotFound bool + executor execx.Executor + paths Paths + serverSide bool + skipNotFound bool + clientVersion bool } -func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound bool) *Runner { +func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound, clientVersion bool) *Runner { return &Runner{ - executor: executor, - paths: paths, - serverSide: serverSide, - skipNotFound: skipNotFound, + executor: executor, + paths: paths, + serverSide: serverSide, + skipNotFound: skipNotFound, + clientVersion: clientVersion, } } @@ -158,7 +160,7 @@ func (r *Runner) Version() (string, error) { args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - if !r.serverSide { + if r.clientVersion { args = append(args, "--client") } diff --git a/internal/tool/kubectl/runner_test.go b/internal/tool/kubectl/runner_test.go index ef36a3f50..805a0b8d2 100644 --- a/internal/tool/kubectl/runner_test.go +++ b/internal/tool/kubectl/runner_test.go @@ -19,7 +19,7 @@ func Test_Runner_Version(t *testing.T) { r := kubectl.NewRunner(execx.NewFakeExecutor(), kubectl.Paths{ Kubectl: "kubectl", WorkDir: os.TempDir(), - }, true, true) + }, true, true, true) got, err := r.Version() if err != nil { diff --git a/internal/tool/runner.go b/internal/tool/runner.go index 5b695902e..202ed2b44 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -75,7 +75,7 @@ func (rf *RunnerFactory) Create(name, version, workDir string) Runner { Kubectl: filepath.Join(rf.paths.Bin, name, version, name), WorkDir: workDir, }, - false, true) + false, true, true) } if name == Kustomize { From 961b1cfe915549a6afa1291d76af907200ea4c6e Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 19 Jan 2023 20:27:24 +0100 Subject: [PATCH 087/383] feat: added cluster connectivity check also in delete distribution phase --- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index b2082ee4f..dcae9e70e 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -33,6 +33,7 @@ const ( var ( errCheckPendingResources = errors.New("error while checking pending resources") errPendingResources = errors.New("pending resources: ") + errClusterConnect = errors.New("error connecting to cluster") ) type Distribution struct { @@ -162,6 +163,12 @@ func (d *Distribution) Exec() error { return nil } + logrus.Info("Checking cluster connectivity...") + + if _, err := d.kubeRunner.Version(); err != nil { + return errClusterConnect + } + logrus.Info("Deleting ingresses...") if err = d.deleteIngresses(); err != nil { From 6ca116308b3940a3c76653d06209ec5d343c3f5f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 11:22:24 +0100 Subject: [PATCH 088/383] feat: handle connectivity check in dry run --- .../kfd/v1alpha2/eks/create/distribution.go | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 8078a8622..9b8c44ef7 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -116,9 +116,21 @@ func NewDistribution( } func (d *Distribution) Exec() error { + timestamp := time.Now().Unix() + logrus.Info("Running distribution phase") - timestamp := time.Now().Unix() + logrus.Info("Checking cluster connectivity...") + + if _, err := d.kubeRunner.Version(); err != nil { + + if !d.dryRun { + return errClusterConnect + } + + logrus.Warnf("Cluster is unreachable, make sure to check connectivity before " + + "running the command without --dry-run") + } if err := d.CreateFolder(); err != nil { return fmt.Errorf("error creating distribution phase folder: %w", err) @@ -203,12 +215,6 @@ func (d *Distribution) Exec() error { return err } - logrus.Info("Checking cluster connectivity...") - - if _, err := d.kubeRunner.Version(); err != nil { - return errClusterConnect - } - logrus.Info("Building manifests...") manifestsOutPath, err := d.buildManifests() From 776476ef86917c2c596d483424a699144f7f9aa4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 11:24:35 +0100 Subject: [PATCH 089/383] fix: connectivity check in cluster delete --- .../apis/kfd/v1alpha2/eks/delete/distribution.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index dcae9e70e..f4ecd8035 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -102,6 +102,12 @@ func (d *Distribution) Exec() error { return nil } + logrus.Info("Checking cluster connectivity...") + + if _, err := d.kubeRunner.Version(); err != nil { + return errClusterConnect + } + if d.dryRun { timestamp := time.Now().Unix() @@ -163,12 +169,6 @@ func (d *Distribution) Exec() error { return nil } - logrus.Info("Checking cluster connectivity...") - - if _, err := d.kubeRunner.Version(); err != nil { - return errClusterConnect - } - logrus.Info("Deleting ingresses...") if err = d.deleteIngresses(); err != nil { From 0a9eafbeca724e9a7eceabd03eb526a894aaaac4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 11:28:25 +0100 Subject: [PATCH 090/383] fix: linting --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 9b8c44ef7..3c0008f80 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -123,7 +123,6 @@ func (d *Distribution) Exec() error { logrus.Info("Checking cluster connectivity...") if _, err := d.kubeRunner.Version(); err != nil { - if !d.dryRun { return errClusterConnect } From 8d2cb38b548a7b60c944211f29ff1768ed43a5bc Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Tue, 24 Jan 2023 09:45:08 +0100 Subject: [PATCH 091/383] fix: don't chdir when running kubectl version --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 5 +++-- internal/tool/kubectl/runner.go | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 3c0008f80..bab04414b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -120,14 +120,15 @@ func (d *Distribution) Exec() error { logrus.Info("Running distribution phase") - logrus.Info("Checking cluster connectivity...") + logrus.Info("Checking that the cluster is reachable...") if _, err := d.kubeRunner.Version(); err != nil { if !d.dryRun { return errClusterConnect } + logrus.Debugf("Got error while running cluster reachability check: %s", err) - logrus.Warnf("Cluster is unreachable, make sure to check connectivity before " + + logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + "running the command without --dry-run") } diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index dc047558a..6386a04df 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -167,7 +167,6 @@ func (r *Runner) Version() (string, error) { out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ Args: args, Executor: r.executor, - WorkDir: r.paths.WorkDir, })) if err != nil { return "", fmt.Errorf("error getting kubectl version: %w", err) From 8e10b73cf78141ff266df146fa40fa4b5eccb010 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Tue, 24 Jan 2023 09:48:28 +0100 Subject: [PATCH 092/383] fix: print err also when not in dryrun --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index bab04414b..5b14e8078 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -123,10 +123,11 @@ func (d *Distribution) Exec() error { logrus.Info("Checking that the cluster is reachable...") if _, err := d.kubeRunner.Version(); err != nil { + logrus.Debugf("Got error while running cluster reachability check: %s", err) + if !d.dryRun { return errClusterConnect } - logrus.Debugf("Got error while running cluster reachability check: %s", err) logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + "running the command without --dry-run") From eae06831fc0e0897a3c95a8e285feb0c22888d6f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:03:24 +0100 Subject: [PATCH 093/383] Feat: switched to output json in command kubectl version (#182) * feat: switched to output json in command kubectl version * fix: unit/e2e tests * fix: unit/e2e tests pt.2 --- cmd/validate/dependencies.go | 9 +++++++++ internal/dependencies/tools/kubectl.go | 4 ++-- .../tools/test_data/kubectl/1.21.1/kubectl | 14 +++++++++++++- .../tools/test_data/kubectl/1.22.0/kubectl | 14 +++++++++++++- internal/dependencies/tools/tool_test.go | 17 +++++++++++++---- internal/tool/kubectl/runner.go | 2 ++ .../dependencies/correct/kubectl/1.24.9/kubectl | 14 +++++++++++++- .../dependencies/wrong/kubectl/1.24.9/kubectl | 14 +++++++++++++- 8 files changed, 78 insertions(+), 10 deletions(-) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 3969b7fa0..bab5d7459 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -46,6 +46,8 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { dloader := distribution.NewDownloader(netx.NewGoGetterClient()) + // Download the distribution. + logrus.Info("Downloading distribution...") dres, err := dloader.Download(distroLocation, furyctlPath) if err != nil { cmdEvent.AddErrorMessage(err) @@ -69,10 +71,17 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { } toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath) + envVarsValidator := envvars.NewValidator() + errs := make([]error, 0) + logrus.Info("Validating tools...") + toks, terrs := toolsValidator.Validate(dres.DistroManifest) + + logrus.Info("Validating environment variables...") + eoks, eerrs := envVarsValidator.Validate(dres.MinimalConf.Kind) errs = append(errs, terrs...) diff --git a/internal/dependencies/tools/kubectl.go b/internal/dependencies/tools/kubectl.go index 4b868811f..e28d8ecc8 100644 --- a/internal/dependencies/tools/kubectl.go +++ b/internal/dependencies/tools/kubectl.go @@ -20,11 +20,11 @@ func NewKubectl(runner *kubectl.Runner, version string) *Kubectl { os: runtime.GOOS, version: version, checker: &checker{ - regex: regexp.MustCompile("GitVersion:\"([^\"]*)\""), + regex: regexp.MustCompile("\"gitVersion\": \"v([^\"]*)\""), runner: runner, trimFn: func(tokens []string) string { return strings.TrimRight( - strings.TrimLeft(tokens[len(tokens)-1], "\"v"), + strings.TrimLeft(tokens[len(tokens)-1], " \"v"), "\"", ) }, diff --git a/internal/dependencies/tools/test_data/kubectl/1.21.1/kubectl b/internal/dependencies/tools/test_data/kubectl/1.21.1/kubectl index fbca431af..2d3b1c6e6 100755 --- a/internal/dependencies/tools/test_data/kubectl/1.21.1/kubectl +++ b/internal/dependencies/tools/test_data/kubectl/1.21.1/kubectl @@ -1,5 +1,17 @@ #!/bin/sh cat < Date: Wed, 25 Jan 2023 10:38:29 +0100 Subject: [PATCH 094/383] fix: returns instead of replacing the ovpn file with blank (#186) --- internal/tool/furyagent/runner.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index 4d0ebd755..bbb5e800f 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -46,6 +46,8 @@ func (r *Runner) ConfigOpenvpnClient(name string) error { if !strings.Contains(err.Error(), "already exists") { return fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) } + + return nil } err := os.WriteFile(path.Join(r.paths.WorkDir, From 4457e5bdb97702c6afadb71dc974f45f686ddaac Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:42:18 +0100 Subject: [PATCH 095/383] Feat: added support to LogRetentionDays and NodePoolsLaunchKind (#189) * feat: added support to LogRetentionDays and NodePoolsLaunchKind * fix: update all furyctl.yaml inside tests * fix: update test data * chore: upgrade to fury-distribution dep * chore: update internal/apis/kfd/v1alpha2/eks/create/kubernetes.go Co-authored-by: Ramiro Algozino Co-authored-by: Ramiro Algozino --- go.mod | 2 +- go.sum | 4 +- .../kfd/v1alpha2/eks/create/kubernetes.go | 201 ++++++++++-------- .../data/e2e/create/cluster/data/furyctl.yaml | 1 + .../data/schemas/ekscluster-kfd-v1alpha2.json | 29 ++- .../config/default/data/expected-furyctl.yaml | 1 + .../config/ekscluster-kfd-v1alpha2.yaml.tpl | 1 + .../dependencies/v1.24.1/furyctl.yaml | 1 + .../template/complex-dry-run/furyctl.yaml | 1 + .../e2e/dump/template/complex/furyctl.yaml | 1 + .../e2e/validate/config/correct/furyctl.yaml | 1 + .../schemas/ekscluster-kfd-v1alpha2.json | 29 ++- .../e2e/validate/config/nodistro/furyctl.yaml | 1 + .../e2e/validate/config/wrong/furyctl.yaml | 1 + .../schemas/ekscluster-kfd-v1alpha2.json | 29 ++- .../dependencies/correct/furyctl.yaml | 1 + .../dependencies/missing/furyctl.yaml | 1 + .../validate/dependencies/wrong/furyctl.yaml | 1 + .../create-complete/data/furyctl.yaml | 1 + .../create-skip-infra/data/furyctl.yaml | 1 + .../create-skip-kube/data/furyctl.yaml | 1 + test/data/integration/v1.24.1/furyctl.yaml | 1 + 22 files changed, 195 insertions(+), 115 deletions(-) diff --git a/go.mod b/go.mod index 472e18e7c..e5ad0f5a8 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/onsi/ginkgo/v2 v2.6.1 github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 - github.com/sighupio/fury-distribution v1.24.1-0.20230120183200-c7572efd3b23 + github.com/sighupio/fury-distribution v1.24.1-0.20230125135610-4bbbb88e5ffa golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index bae420529..a7e1ec90e 100644 --- a/go.sum +++ b/go.sum @@ -286,8 +286,8 @@ github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdk github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.24.1-0.20230120183200-c7572efd3b23 h1:x0USah6e/HTe5QNyp/mqFdFS67LLP2nNYY9jTLxhq54= -github.com/sighupio/fury-distribution v1.24.1-0.20230120183200-c7572efd3b23/go.mod h1:MpW/zGMlwJIQoBgZ6xWwL6RN6j7N5bCEX8949p3Pfik= +github.com/sighupio/fury-distribution v1.24.1-0.20230125135610-4bbbb88e5ffa h1:7TOzkZAqx0VswNGsuqB/VnnegLv0q83qAfvVoO7uA0w= +github.com/sighupio/fury-distribution v1.24.1-0.20230125135610-4bbbb88e5ffa/go.mod h1:MpW/zGMlwJIQoBgZ6xWwL6RN6j7N5bCEX8949p3Pfik= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 6bdb57624..4d3a93b71 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -282,6 +282,26 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + err = bytesx.SafeWriteToBuffer( + &buffer, + "node_pools_launch_kind = \"%v\"\n", + k.furyctlConf.Spec.Kubernetes.NodePoolsLaunchKind, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if k.furyctlConf.Spec.Kubernetes.LogRetentionDays != nil { + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_log_retention_days = %v\n", + *k.furyctlConf.Spec.Kubernetes.LogRetentionDays, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + if vpcIDSource == nil { if !k.dryRun { return errVpcIDNotFound @@ -356,91 +376,9 @@ func (k *Kubernetes) createTfVars() error { } } - if k.furyctlConf.Spec.Kubernetes.AwsAuth != nil { - if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts) > 0 { - err = bytesx.SafeWriteToBuffer( - &buffer, - "eks_map_accounts = [\"%v\"]\n", - strings.Join(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts, "\",\""), - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - - if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users) > 0 { - err = bytesx.SafeWriteToBuffer( - &buffer, - "eks_map_users = [\n", - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - for i, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Users { - content := "{\ngroups = [\"%v\"]\nusername = \"%v\"\nuserarn = \"%v\"}" - - if i < len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users)-1 { - content += "," - } - - err = bytesx.SafeWriteToBuffer( - &buffer, - content, - strings.Join(account.Groups, "\",\""), - account.Username, - account.Userarn, - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - - err = bytesx.SafeWriteToBuffer( - &buffer, - "]\n", - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - - if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles) > 0 { - err = bytesx.SafeWriteToBuffer( - &buffer, - "eks_map_roles = [\n", - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - for i, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles { - content := "{\ngroups = [\"%v\"]\nusername = \"%v\"\nrolearn = \"%v\"}" - - if i < len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles)-1 { - content += "," - } - - err = bytesx.SafeWriteToBuffer( - &buffer, - content, - strings.Join(account.Groups, "\",\""), - account.Username, - account.Rolearn, - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - - err = bytesx.SafeWriteToBuffer( - &buffer, - "]\n", - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } + err = k.addAwsAuthToTfVars(&buffer) + if err != nil { + return fmt.Errorf("error writing AWS Auth to Terraform vars file: %w", err) } if len(k.furyctlConf.Spec.Kubernetes.NodePools) > 0 { @@ -681,6 +619,99 @@ func (k *Kubernetes) createTfVars() error { return nil } +func (k *Kubernetes) addAwsAuthToTfVars(buffer *bytes.Buffer) error { + var err error + + if k.furyctlConf.Spec.Kubernetes.AwsAuth != nil { + if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts) > 0 { + err = bytesx.SafeWriteToBuffer( + buffer, + "eks_map_accounts = [\"%v\"]\n", + strings.Join(k.furyctlConf.Spec.Kubernetes.AwsAuth.AdditionalAccounts, "\",\""), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users) > 0 { + err = bytesx.SafeWriteToBuffer( + buffer, + "eks_map_users = [\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for i, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Users { + content := "{\ngroups = [\"%v\"]\nusername = \"%v\"\nuserarn = \"%v\"}" + + if i < len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Users)-1 { + content += "," + } + + err = bytesx.SafeWriteToBuffer( + buffer, + content, + strings.Join(account.Groups, "\",\""), + account.Username, + account.Userarn, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + err = bytesx.SafeWriteToBuffer( + buffer, + "]\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles) > 0 { + err = bytesx.SafeWriteToBuffer( + buffer, + "eks_map_roles = [\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + for i, account := range k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles { + content := "{\ngroups = [\"%v\"]\nusername = \"%v\"\nrolearn = \"%v\"}" + + if i < len(k.furyctlConf.Spec.Kubernetes.AwsAuth.Roles)-1 { + content += "," + } + + err = bytesx.SafeWriteToBuffer( + buffer, + content, + strings.Join(account.Groups, "\",\""), + account.Username, + account.Rolearn, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + err = bytesx.SafeWriteToBuffer( + buffer, + "]\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + } + + return nil +} + func (*Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np schema.SpecKubernetesNodePool) error { if len(np.AdditionalFirewallRules) > 0 { err := bytesx.SafeWriteToBuffer( diff --git a/test/data/e2e/create/cluster/data/furyctl.yaml b/test/data/e2e/create/cluster/data/furyctl.yaml index 77aacd2f7..8981a8413 100644 --- a/test/data/e2e/create/cluster/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/data/furyctl.yaml @@ -49,6 +49,7 @@ spec: - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker ami: diff --git a/test/data/e2e/create/cluster/data/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/create/cluster/data/schemas/ekscluster-kfd-v1alpha2.json index 37842f285..756a63d49 100644 --- a/test/data/e2e/create/cluster/data/schemas/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/create/cluster/data/schemas/ekscluster-kfd-v1alpha2.json @@ -355,6 +355,17 @@ } ] }, + "nodePoolsLaunchKind": { + "type": "string", + "enum": [ + "launch_configurations", + "launch_templates", + "both" + ] + }, + "logRetentionDays": { + "type": "integer" + }, "nodePools": { "type": "array", "items": { @@ -367,7 +378,8 @@ }, "required": [ "nodeAllowedSshPublicKey", - "nodePools" + "nodePools", + "nodePoolsLaunchKind" ] }, "Spec.Kubernetes.APIServerEndpointAccess": { @@ -440,7 +452,6 @@ } }, "required": [ - "ami", "instance", "name", "size" @@ -516,7 +527,8 @@ "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" - } + }, + "minItems": 1 }, "protocol": { "$ref": "#/$defs/Types.AwsIpProtocol" @@ -1137,6 +1149,9 @@ "provider": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" }, + "baseDomain": { + "type": "string" + }, "pomerium": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" }, @@ -1161,7 +1176,7 @@ "then": { "properties": { "auth": { - "required": ["dex", "pomerium"] + "required": ["dex", "pomerium", "baseDomain"] } } }, @@ -1365,14 +1380,10 @@ "properties": { "iamRoleArn": { "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" } }, "required": [ - "iamRoleArn", - "region" + "iamRoleArn" ] }, diff --git a/test/data/e2e/create/config/default/data/expected-furyctl.yaml b/test/data/e2e/create/config/default/data/expected-furyctl.yaml index cbf1ee6e3..da2714bc0 100644 --- a/test/data/e2e/create/config/default/data/expected-furyctl.yaml +++ b/test/data/e2e/create/config/default/data/expected-furyctl.yaml @@ -59,6 +59,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker ami: diff --git a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl index 755be9510..8c7c13ee6 100644 --- a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl +++ b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl @@ -59,6 +59,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker ami: diff --git a/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml b/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml index 15b304920..5e1e6fde9 100644 --- a/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml +++ b/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml @@ -58,6 +58,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker ami: diff --git a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml index cc2e652b9..55672719d 100644 --- a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml @@ -71,6 +71,7 @@ spec: apiServerAllowedCidrs: - 10.1.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/e2e/dump/template/complex/furyctl.yaml b/test/data/e2e/dump/template/complex/furyctl.yaml index b7b2f52da..248910ced 100644 --- a/test/data/e2e/dump/template/complex/furyctl.yaml +++ b/test/data/e2e/dump/template/complex/furyctl.yaml @@ -71,6 +71,7 @@ spec: apiServerAllowedCidrs: - 10.1.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index 77aacd2f7..8981a8413 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -49,6 +49,7 @@ spec: - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker ami: diff --git a/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json index 37842f285..756a63d49 100644 --- a/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json @@ -355,6 +355,17 @@ } ] }, + "nodePoolsLaunchKind": { + "type": "string", + "enum": [ + "launch_configurations", + "launch_templates", + "both" + ] + }, + "logRetentionDays": { + "type": "integer" + }, "nodePools": { "type": "array", "items": { @@ -367,7 +378,8 @@ }, "required": [ "nodeAllowedSshPublicKey", - "nodePools" + "nodePools", + "nodePoolsLaunchKind" ] }, "Spec.Kubernetes.APIServerEndpointAccess": { @@ -440,7 +452,6 @@ } }, "required": [ - "ami", "instance", "name", "size" @@ -516,7 +527,8 @@ "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" - } + }, + "minItems": 1 }, "protocol": { "$ref": "#/$defs/Types.AwsIpProtocol" @@ -1137,6 +1149,9 @@ "provider": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" }, + "baseDomain": { + "type": "string" + }, "pomerium": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" }, @@ -1161,7 +1176,7 @@ "then": { "properties": { "auth": { - "required": ["dex", "pomerium"] + "required": ["dex", "pomerium", "baseDomain"] } } }, @@ -1365,14 +1380,10 @@ "properties": { "iamRoleArn": { "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" } }, "required": [ - "iamRoleArn", - "region" + "iamRoleArn" ] }, diff --git a/test/data/e2e/validate/config/nodistro/furyctl.yaml b/test/data/e2e/validate/config/nodistro/furyctl.yaml index f50652a1a..d655a48d1 100644 --- a/test/data/e2e/validate/config/nodistro/furyctl.yaml +++ b/test/data/e2e/validate/config/nodistro/furyctl.yaml @@ -80,6 +80,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/e2e/validate/config/wrong/furyctl.yaml b/test/data/e2e/validate/config/wrong/furyctl.yaml index 300b64100..1bba7fa33 100644 --- a/test/data/e2e/validate/config/wrong/furyctl.yaml +++ b/test/data/e2e/validate/config/wrong/furyctl.yaml @@ -81,6 +81,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json index 37842f285..756a63d49 100644 --- a/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json @@ -355,6 +355,17 @@ } ] }, + "nodePoolsLaunchKind": { + "type": "string", + "enum": [ + "launch_configurations", + "launch_templates", + "both" + ] + }, + "logRetentionDays": { + "type": "integer" + }, "nodePools": { "type": "array", "items": { @@ -367,7 +378,8 @@ }, "required": [ "nodeAllowedSshPublicKey", - "nodePools" + "nodePools", + "nodePoolsLaunchKind" ] }, "Spec.Kubernetes.APIServerEndpointAccess": { @@ -440,7 +452,6 @@ } }, "required": [ - "ami", "instance", "name", "size" @@ -516,7 +527,8 @@ "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" - } + }, + "minItems": 1 }, "protocol": { "$ref": "#/$defs/Types.AwsIpProtocol" @@ -1137,6 +1149,9 @@ "provider": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" }, + "baseDomain": { + "type": "string" + }, "pomerium": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" }, @@ -1161,7 +1176,7 @@ "then": { "properties": { "auth": { - "required": ["dex", "pomerium"] + "required": ["dex", "pomerium", "baseDomain"] } } }, @@ -1365,14 +1380,10 @@ "properties": { "iamRoleArn": { "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" } }, "required": [ - "iamRoleArn", - "region" + "iamRoleArn" ] }, diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index f50652a1a..d655a48d1 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -80,6 +80,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/e2e/validate/dependencies/missing/furyctl.yaml b/test/data/e2e/validate/dependencies/missing/furyctl.yaml index f50652a1a..d655a48d1 100644 --- a/test/data/e2e/validate/dependencies/missing/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/missing/furyctl.yaml @@ -80,6 +80,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/e2e/validate/dependencies/wrong/furyctl.yaml b/test/data/e2e/validate/dependencies/wrong/furyctl.yaml index 615611cca..7c8abc066 100644 --- a/test/data/e2e/validate/dependencies/wrong/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/wrong/furyctl.yaml @@ -82,6 +82,7 @@ spec: allowedCidrs: - 10.1.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker # optional, was OS, and version was removed diff --git a/test/data/expensive/create-complete/data/furyctl.yaml b/test/data/expensive/create-complete/data/furyctl.yaml index 2ce970c43..73536c9d9 100644 --- a/test/data/expensive/create-complete/data/furyctl.yaml +++ b/test/data/expensive/create-complete/data/furyctl.yaml @@ -49,6 +49,7 @@ spec: - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker-eks ami: diff --git a/test/data/expensive/create-skip-infra/data/furyctl.yaml b/test/data/expensive/create-skip-infra/data/furyctl.yaml index efb32b7b4..6f6b10ded 100644 --- a/test/data/expensive/create-skip-infra/data/furyctl.yaml +++ b/test/data/expensive/create-skip-infra/data/furyctl.yaml @@ -49,6 +49,7 @@ spec: - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker-eks ami: diff --git a/test/data/expensive/create-skip-kube/data/furyctl.yaml b/test/data/expensive/create-skip-kube/data/furyctl.yaml index 476b3c01c..c169e96c1 100644 --- a/test/data/expensive/create-skip-kube/data/furyctl.yaml +++ b/test/data/expensive/create-skip-kube/data/furyctl.yaml @@ -49,6 +49,7 @@ spec: - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker-eks ami: diff --git a/test/data/integration/v1.24.1/furyctl.yaml b/test/data/integration/v1.24.1/furyctl.yaml index 15b304920..5e1e6fde9 100644 --- a/test/data/integration/v1.24.1/furyctl.yaml +++ b/test/data/integration/v1.24.1/furyctl.yaml @@ -58,6 +58,7 @@ spec: allowedCidrs: - 10.0.0.0/16 nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + nodePoolsLaunchKind: "launch_templates" nodePools: - name: worker ami: From 17e1162619fb3752c20aeaf0b06e463d5e2c2f93 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 27 Jan 2023 11:50:51 +0100 Subject: [PATCH 096/383] feat: support tty in external commands (#192) --- cmd/create/cluster.go | 8 ++ cmd/delete/cluster.go | 141 +++++++++++++++++++----------- cmd/root.go | 9 +- internal/tool/terraform/runner.go | 16 +++- internal/x/exec/cmd.go | 1 + internal/x/logrus/config.go | 5 +- 6 files changed, 119 insertions(+), 61 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 384f26a9e..77cf96599 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -42,6 +42,7 @@ type ClusterCmdFlags struct { DryRun bool SkipDepsDownload bool SkipDepsValidation bool + NoTTY bool Kubeconfig string } @@ -98,6 +99,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Init packages. execx.Debug = flags.Debug || flags.DryRun + execx.NoTTY = flags.NoTTY // Download the distribution. logrus.Info("Downloading distribution...") @@ -267,6 +269,11 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "dry-run") } + noTTY, err := cmdutil.BoolFlag(cmd, "no-tty", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "no-tty") + } + skipDepsDownload, err := cmdutil.BoolFlag(cmd, "skip-deps-download", tracker, cmdEvent) if err != nil { return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-deps-download") @@ -293,6 +300,7 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm DryRun: dryRun, SkipDepsDownload: skipDepsDownload, SkipDepsValidation: skipDepsValidation, + NoTTY: noTTY, Kubeconfig: kubeconfig, }, nil } diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 517775cf3..87f178839 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -30,6 +30,18 @@ var ( ErrKubeconfigReq = errors.New("when running distribution phase, either the KUBECONFIG environment variable or the --kubeconfig flag should be set") ) +type ClusterCmdFlags struct { + Debug bool + FuryctlPath string + DistroLocation string + Phase string + BinPath string + Force bool + DryRun bool + NoTTY bool + Kubeconfig string +} + func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { var cmdEvent analytics.Event @@ -40,45 +52,10 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, RunE: func(cmd *cobra.Command, _ []string) error { - debug, err := cmdutil.BoolFlag(cmd, "debug", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: debug", ErrParsingFlag) - } - - furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: config", ErrParsingFlag) - } - - distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: distro-location", ErrParsingFlag) - } - - phase, err := cmdutil.StringFlag(cmd, "phase", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: phase", ErrParsingFlag) - } - - err = cluster.CheckPhase(phase) + // Get flags. + flags, err := getDeleteClusterCmdFlags(cmd, tracker, cmdEvent) if err != nil { - return fmt.Errorf("%w: %s: %s", ErrParsingFlag, "phase", err.Error()) - } - - binPath := cobrax.Flag[string](cmd, "bin-path").(string) //nolint:errcheck,forcetypeassert // optional flag - dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: dry-run", ErrParsingFlag) - } - - force, err := cmdutil.BoolFlag(cmd, "force", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: force", ErrParsingFlag) - } - - kubeconfig, err := cmdutil.StringFlag(cmd, "kubeconfig", tracker, cmdEvent) - if err != nil { - return fmt.Errorf("%w: %s", ErrParsingFlag, "kubeconfig") + return err } // Init paths. @@ -88,8 +65,8 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { } // Check if kubeconfig is needed. - if phase == cluster.OperationPhaseDistribution || phase == cluster.OperationPhaseAll { - if kubeconfig == "" { + if flags.Phase == cluster.OperationPhaseDistribution || flags.Phase == cluster.OperationPhaseAll { + if flags.Kubeconfig == "" { kubeconfigFromEnv := os.Getenv("KUBECONFIG") if kubeconfigFromEnv == "" { @@ -100,8 +77,8 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { } } - if binPath == "" { - binPath = filepath.Join(homeDir, ".furyctl", "bin") + if flags.BinPath == "" { + flags.BinPath = filepath.Join(homeDir, ".furyctl", "bin") } // Init first half of collaborators. @@ -109,11 +86,12 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { executor := execx.NewStdExecutor() distrodl := distribution.NewDownloader(client) - execx.Debug = debug || dryRun + execx.Debug = flags.Debug || flags.DryRun + execx.NoTTY = flags.NoTTY // Download the distribution. logrus.Info("Downloading distribution...") - res, err := distrodl.Download(distroLocation, furyctlPath) + res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) if err != nil { err = fmt.Errorf("error while downloading distribution: %w", err) @@ -126,7 +104,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) // Init second half of collaborators. - depsvl := dependencies.NewValidator(executor, binPath) + depsvl := dependencies.NewValidator(executor, flags.BinPath) // Validate the dependencies. logrus.Info("Validating dependencies...") @@ -137,10 +115,10 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { clusterDeleter, err := cluster.NewDeleter( res.MinimalConf, res.DistroManifest, - phase, + flags.Phase, basePath, - binPath, - kubeconfig, + flags.BinPath, + flags.Kubeconfig, ) if err != nil { cmdEvent.AddErrorMessage(err) @@ -149,7 +127,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while initializing cluster deleter: %w", err) } - if !force { + if !flags.Force { _, err = fmt.Println("WARNING: You are about to delete a cluster. This action is irreversible.") if err != nil { cmdEvent.AddErrorMessage(err) @@ -171,7 +149,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { } } - err = clusterDeleter.Delete(dryRun) + err = clusterDeleter.Delete(flags.DryRun) if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) @@ -179,7 +157,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while deleting cluster: %w", err) } - if !dryRun && phase == cluster.OperationPhaseAll { + if !flags.DryRun && flags.Phase == cluster.OperationPhaseAll { logrus.Info("Cluster deleted successfully!") } @@ -243,6 +221,67 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return cmd } +func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (ClusterCmdFlags, error) { + debug, err := cmdutil.BoolFlag(cmd, "debug", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "debug") + } + + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "config") + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "distro-location") + } + + phase, err := cmdutil.StringFlag(cmd, "phase", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "phase") + } + + err = cluster.CheckPhase(phase) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s: %s", ErrParsingFlag, "phase", err.Error()) + } + + binPath := cmdutil.StringFlagOptional(cmd, "bin-path") + + dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "dry-run") + } + + noTTY, err := cmdutil.BoolFlag(cmd, "no-tty", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "no-tty") + } + + kubeconfig, err := cmdutil.StringFlag(cmd, "kubeconfig", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "kubeconfig") + } + + force, err := cmdutil.BoolFlag(cmd, "force", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: force", ErrParsingFlag) + } + + return ClusterCmdFlags{ + Debug: debug, + FuryctlPath: furyctlPath, + DistroLocation: distroLocation, + Phase: phase, + BinPath: binPath, + DryRun: dryRun, + Force: force, + NoTTY: noTTY, + Kubeconfig: kubeconfig, + }, nil +} + func askForConfirmation() bool { reader := bufio.NewReader(os.Stdin) diff --git a/cmd/root.go b/cmd/root.go index 247007e72..0c6e0ccfd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -89,11 +89,8 @@ Furyctl is a simple CLI tool to: w := logrus.StandardLogger().Out cflag, ok := cobrax.Flag[bool](cmd, "no-tty").(bool) - if ok && cflag { - w = iox.NewNullWriter() - f := new(logrus.TextFormatter) - f.DisableColors = true - logrus.SetFormatter(f) + if !ok { + logrus.Fatalf("error while getting no-tty flag") } cfg.Spinner = spinner.New(spinner.CharSets[spinnerStyle], timeout, spinner.WithWriter(w)) @@ -120,7 +117,7 @@ Furyctl is a simple CLI tool to: // Set log level. dflag, ok := cobrax.Flag[bool](cmd, "debug").(bool) if ok { - logrusx.InitLog(logFile, dflag) + logrusx.InitLog(logFile, dflag, cflag) } logrus.Debugf("logging to: %s", logPath) diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index 50d2c2e03..1df059998 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -49,8 +49,14 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Init() error { + args := []string{"init"} + + if execx.NoTTY { + args = append(args, "-no-color") + } + err := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: []string{"init"}, + Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, }).Run() @@ -139,8 +145,14 @@ func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { } func (r *Runner) Destroy() error { + args := []string{"destroy", "-auto-approve"} + + if execx.NoTTY { + args = append(args, "-no-color") + } + err := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: []string{"destroy", "-auto-approve"}, + Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, }).Run() diff --git a/internal/x/exec/cmd.go b/internal/x/exec/cmd.go index a41695ce6..44a07eee7 100644 --- a/internal/x/exec/cmd.go +++ b/internal/x/exec/cmd.go @@ -19,6 +19,7 @@ import ( var ( Debug = false //nolint:gochecknoglobals // This variable is shared between all the command instances. LogFile *os.File //nolint:gochecknoglobals // This variable is shared between all the command instances. + NoTTY = false //nolint:gochecknoglobals // This variable is shared between all the command instances. ErrCmdFailed = errors.New("command failed") ErrCmdTimeout = errors.New("command timed out") ) diff --git a/internal/x/logrus/config.go b/internal/x/logrus/config.go index 3757e784d..d767b666c 100644 --- a/internal/x/logrus/config.go +++ b/internal/x/logrus/config.go @@ -44,7 +44,7 @@ func newFormatterHook(writer io.Writer, formatter logrus.Formatter, logLevels [] } } -func InitLog(logFile *os.File, debug bool) { //nolint:revive // debug is a boolean flag +func InitLog(logFile *os.File, debug, disableColors bool) { //nolint:revive // debug is a boolean flag logrus.SetOutput(io.Discard) stdLevels := []logrus.Level{ @@ -67,7 +67,8 @@ func InitLog(logFile *os.File, debug bool) { //nolint:revive // debug is a boole stdOutHook := newFormatterHook(os.Stdout, &logrus.TextFormatter{ DisableTimestamp: true, - ForceColors: true, + ForceColors: !disableColors, + DisableColors: disableColors, }, stdLevels) logrus.AddHook(stdOutHook) From 666d869b9a9ac08c469945237d55afd3e5ebfc3c Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 17 Jan 2023 17:59:09 +0100 Subject: [PATCH 097/383] feat: first stub --- README.md | 232 ++++++++++++----------------------- cmd/create/config.go | 10 +- cmd/download.go | 2 +- cmd/download/dependencies.go | 2 +- cmd/validate.go | 2 +- cmd/validate/dependencies.go | 2 +- 6 files changed, 87 insertions(+), 163 deletions(-) diff --git a/README.md b/README.md index 7e1211a65..fef19ad70 100644 --- a/README.md +++ b/README.md @@ -78,203 +78,127 @@ See the available commands with `furyctl --help`: ```bash furyctl --help -A command-line tool to manage cluster deployment with Kubernetes +The multi-purpose command line tool for the Kubernetes Fury Distribution. + +Furyctl is a simple CLI tool to: + +- download and manage the Kubernetes Fury Distribution (KFD) modules +- create and manage Kubernetes Fury clusters Usage: furyctl [command] Available Commands: - bootstrap Creates the required infrastructure to deploy a battle-tested Kubernetes cluster, mostly network components - cluster Creates a battle-tested Kubernetes cluster completion Generate completion script + create Create a cluster or a config file + delete Delete a cluster + download Download all dependencies from the Kubernetes Fury Distribution specified in the config file + dump Dump templates and other useful fury objects help Help about any command - init Initialize the minimum distribution configuration - vendor Download dependencies specified in Furyfile.yml - version Prints the client version information + validate Validate the config file and the dependencies relative to the Kubernetes Fury Distribution specified in it + version Print the version number of furyctl + +Flags: + -D, --debug Enables furyctl debug output + -d, --disable-analytics Disable analytics + -h, --help help for furyctl + -l, --log string Path to the log file or stdout to log to standard output (default: ~/.furyctl/furyctl.log) + -T, --no-tty Disable TTY + -w, --workdir string Switch to a different working directory before executing the given subcommand. + +Use "furyctl [command] --help" for more information about a command. ``` ## Download and manage KFD modules `furyctl` can be used as a package manager for the KFD. -It provides a simple way to download all the desired modules of the KFD by reading a single `Furyfile`. +It provides a simple way to download all the desired modules of the KFD by reading a single `furyctl.yaml`. The process requires the following steps: -1. Write a `Furyfile` -2. Run `furyctl vendor` to download all the modules - -### 1. Write a Furyfile - -A `Furyfile` is a simple YAML formatted file that lists which modules (and versions) of the KFD you want to download. - -An example `Furyfile` is the following: - -```yaml -# Here you can specify which versions of the modules to use -versions: - networking: v1.7.0 - monitoring: v1.13.0 - logging: v1.9.1 - ingress: v1.11.2 - dr: v1.8.0 - opa: v1.5.0 - -# The bases are a sets of Kustomize bases to deploy Kubernetes components -bases: - - name: networking/ - - name: monitoring/ - - name: logging/ - - name: ingress/ - - name: dr/ - - name: opa/ -``` +1. Generate a `furyctl.yaml` by running `furyctl create config` specifying the desired Kubernetes Fury Distribution version + with the flag `--version`. +2. Run `furyctl download dependencies` to download all the dependencies including the modules of the KFD. -Each module is composed of a set of packages. In the previous `Furyfile`, we downloaded all packages of each module. You can cherry-pick single packages using the `module/package` syntax. - -A more complete `Furyfile` would be: - -```yaml -# Here you can specify which versions of the modules to use -versions: - networking: v1.7.0 - monitoring: v1.13.0 - logging: v1.9.1 - ingress: v1.11.2 - dr: v1.8.0 - opa: v1.5.0 - -# The bases are a sets of Kustomize bases to deploy Kubernetes components -bases: - - name: networking/calico - - name: monitoring/prometheus-operator - - name: monitoring/prometheus-operated - - name: monitoring/grafana - - name: monitoring/goldpinger - - name: monitoring/configs - - name: monitoring/kubeadm-sm - - name: monitoring/kube-proxy-metrics - - name: monitoring/kube-state-metrics - - name: monitoring/node-exporter - - name: monitoring/metrics-server - - name: monitoring/alertmanager-operated - - name: logging/elasticsearch-single - - name: logging/cerebro - - name: logging/curator - - name: logging/fluentd - - name: logging/kibana - - name: ingress/cert-manager - - name: ingress/nginx - - name: ingress/forecastle - - name: dr/velero - - name: opa/gatekeeper -``` +### 1. Customize the `furyctl.yaml` -You can find out what packages are inside each module by referring to each module's documentation. +A `furyctl.yaml` is a YAML formatted file that contains all the information needed to create a Kubernetes Fury cluster. -### 2. Download the modules +Modules are located in the `distribution` section of the `furyctl.yaml` file and can be configured to better fit your needs. -Run `furyctl vendor` (within the same directory where your `Furyfile` is located) to download the modules. +### 2. Download the modules -`furyctl` will download all the packages in a `vendor/` directory. +Run `furyctl download dependencies` (within the same directory where your `furyctl.yaml` is located) to download the modules and all the dependencies +needed to create a Kubernetes Fury cluster. -> 💡 **TIP** +> 🔥 **Advanced User** > -> Use the `-H` flag in the `furyctl vendor` command to download using HTTP(S) instead of the default SSH. This is useful if you are in an environment that restricts the SSH traffic. +> Using the command `furyctl dump template` with the flag `-w` pointing to the local location of the repository `fury-distribution`, +> will run the template engine on the modules and generate the final manifests that will be applied to the cluster. ## Cluster creation -The Cluster creation feature is available via two commands: - -- `furyctl bootstrap`: creates the required networking infrastructure -- `furyctl cluster`: creates a Fury cluster. - -Both commands provide the following subcommands: +The Cluster creation feature is available via the command `furyctl create cluster`. -- `furyctl {bootstrap,cluster} template --provisioner {provisioner_name}`: Creates a `yml` configuration file with some default options making easy replacing these with the right values. -- `furyctl {bootstrap,cluster} init`: Initializes the project that deploys the infrastructure. -- `furyctl {bootstrap,cluster} apply`: Actually creates or updates the infrastructure. -- `furyctl {bootstrap,cluster} destroy`: Destroys the infrastructure. - -The subcommands accept the following options: +The subcommand accept the following options: ```bash --c, --config string: Configuration file path --t, --token string: GitHub token to access enterprise repositories. Contact sales@sighup.io --w, --workdir string: Working directory with all project files -``` - -> 💡 **TIP** -> -> You can use the `--dry-run` flag simulate the execution of a command - -### Configuration file - -The cluster creation feature uses a different configuration file than the `Furyfile.yml`. -While the `Furyfile.yml` file is used by the package-manager features, the cluster creation feature uses a separated `cluster.yml` file: - -```yaml -kind: # Cluster or Bootstrap -metadata: - name: # Name of the deployment. It can be used by the provisioners as a unique identifier. -executor: # This is an optional attribute. It defines the terraform executor to use along with the backend configuration - state: # Optional attribute. It configures the backend configuration file. - backend: # Optional attribute. It configures the backend to use. Default to local - config: # Optional attribute. It configures the configuration of the selected backend configuration. It accepts multiple key values. - # bucket: "my-bucket" # Example - # key: "terraform.tfvars" # Example - # region: "eu-home-1" # Example -provisioner: # Defines what provisioner to use. -spec: {} # Input variables of the provisioner. Read each provisioner definition to understand what are the valid values. +-b, --bin-path string Path to the bin folder where all dependencies are installed +-c, --config string Path to the furyctl.yaml file (default "furyctl.yaml") +--distro-location string Location where to download schemas, defaults and the distribution manifest. It can either be a local path(eg: /path/to/fury/distribution) or a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME). Any format supported by hashicorp/go-getter can be used. +--dry-run Allows to inspect what resources will be created before applying them +-h, --help help for cluster +--kubeconfig string Path to the kubeconfig file, mandatory if you want to run the distribution phase and the KUBECONFIG environment variable is not set +-p, --phase string Limit the execution to a specific phase. options are: infrastructure, kubernetes, distribution +--skip-deps-download Skip downloading the distribution modules, installers and binaries +--skip-deps-validation Skip validating dependencies +--skip-phase string Avoid executing a unwanted phase. options are: infrastructure, kubernetes, distribution. More specifically: + - skipping infrastructure will execute kubernetes and distribution + - skipping kubernetes will only execute distribution + - skipping distribution will execute infrastructure and kubernetes +--vpn-auto-connect When set will automatically connect to the created VPN in the infrastructure phase + +Global Flags: +-D, --debug Enables furyctl debug output +-d, --disable-analytics Disable analytics +-l, --log string Path to the log file or stdout to log to standard output (default: ~/.furyctl/furyctl.log) +-T, --no-tty Disable TTY +-w, --workdir string Switch to a different working directory before executing the given subcommand. ``` ### Deploy a cluster from zero -The following workflow describes a complete setup of a cluster from scratch. -The bootstrap command will create the underlay requirements to deploy a Kubernetes cluster. Most of these -components are network-related stuff. +The following steps will guide you through the process of creating a Kubernetes Fury cluster from zero. -Once the bootstrap process is up to date, the cluster command can be triggered using outputs from the -`bootstrap apply` command. +1. Follow the previous steps to generate a `furyctl.yaml` and download the modules. +2. Edit the `furyctl.yaml` to customize the cluster configuration by filling the sections `infrastructure`, `kubernetes` and `distribution`. +3. Run `furyctl create cluster` to create the cluster. +4. (Optional) Watch the logs of the cluster creation process with `tail -f ~/.furyctl/furyctl.log`. -```bash -+--------------------------+ +--------------------------+ +--------------------------+ +--------------------------+ -| furyctl bootstrap init +-->+ furyctl bootstrap apply +-->+ furyctl cluster init +-->+ furyctl cluster apply | -+--------------------------+ +--------------------------+ +--------------------------+ +--------------------------+ -``` +> 💡 **Alpha ONLY** +> +> You may need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` until the next release of the KFD. ### Deploy a cluster from an already existing infrastructure -The following workflow describes a setup of a cluster using an already existing underlay infrastructure. - -```bash -+--------------------------+ +--------------------------+ -+ furyctl cluster init +-->+ furyctl cluster apply | -+--------------------------+ +--------------------------+ -``` - -### Installers - -To deploy all the infrastructure components `furyctl` uses *installers*. - -> You can use an environment variable to avoid passing the token via console: `FURYCTL_TOKEN`. - -Contact [sales@sighup.io](mailto:sales@sighup.io) to get more details about this feature. +Same as the previous section, but you can skip the `infrastructure` by not filling the section `infrastructure` in the `furyctl.yaml` file and +running `furyctl create cluster --skip-phase infrastructure`. -#### Bootstrap +#### Infrastructure -The available `bootstrap` provisioners are: +The available `infrastructure` provisioners are: -| Provisioner | Description | -| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `aws` | It creates a VPC with all the requirements to deploy a Kubernetes Cluster. It also includes a VPN instance easily manageable by using `furyagent`. | +| Provisioner | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `aws` | It creates a VPC with all the requirements to deploy a Kubernetes Cluster. It also includes a VPN instance easily manageable by using `furyagent`. | -#### Clusters +#### Kubernetes -The available `cluster` provisioners are: +The available `kubernetes` provisioners are: -| Provisioner | Description | -| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `eks` | Creates an EKS cluster on an already existing VPC. It uses the [fury-eks-installer](https://github.com/sighupio/fury-eks-installer) | +| Provisioner | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `eks` | Creates an EKS cluster on an already existing VPC. It uses the [fury-eks-installer](https://github.com/sighupio/fury-eks-installer) | diff --git a/cmd/create/config.go b/cmd/create/config.go index 1cba67c9d..32f513925 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -29,7 +29,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "config", - Short: "scaffolds a new furyctl config file", + Short: "Scaffolds a new furyctl config file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, @@ -148,28 +148,28 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "version", "v", "", - "fury version to use (eg: v1.24.1)", + "Kubernetes Fury Distribution version to use (eg: v1.24.1)", ) cmd.Flags().StringP( "kind", "k", "EKSCluster", - "type of cluster to create (eg: EKSCluster)", + "Type of cluster to create (eg: EKSCluster)", ) cmd.Flags().StringP( "api-version", "a", "kfd.sighup.io/v1alpha2", - "version of the api to use for the selected kind (eg: kfd.sighup.io/v1alpha2)", + "Version of the api to use for the selected kind (eg: kfd.sighup.io/v1alpha2)", ) cmd.Flags().StringP( "name", "n", "example", - "name of cluster to create", + "Name of cluster to create", ) return cmd diff --git a/cmd/download.go b/cmd/download.go index b7857a1c8..c66bf489b 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -14,7 +14,7 @@ import ( func NewDownloadCmd(tracker *analytics.Tracker) *cobra.Command { dumpCmd := &cobra.Command{ Use: "download", - Short: "Dowload fury files", + Short: "Download all dependencies from the Kubernetes Fury Distribution specified in the config file", } dumpCmd.AddCommand(download.NewDependenciesCmd(tracker)) diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 04b34cdb9..19189f6ee 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -31,7 +31,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", - Short: "Download dependencies", + Short: "Download all dependencies from the Fury Distribution specified in the config file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, diff --git a/cmd/validate.go b/cmd/validate.go index a6bf38420..2c80a0e91 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -14,7 +14,7 @@ import ( func NewValidateCommand(tracker *analytics.Tracker) *cobra.Command { validateCmd := &cobra.Command{ Use: "validate", - Short: "Validate fury config files and dependencies", + Short: "Validate the config file and the dependencies relative to the Kubernetes Fury Distribution specified in it", } validateCmd.AddCommand(validate.NewConfigCmd(tracker)) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index bab5d7459..3741591af 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -29,7 +29,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", - Short: "Validate furyctl.yaml file", + Short: "Validate dependencies from the Kubernetes Fury Distribution specified in the config file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, From 312682b472110e471b97c967543fc557d8df07a4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 18 Jan 2023 15:50:42 +0100 Subject: [PATCH 098/383] chore: added more info about phases --- README.md | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fef19ad70..cf6e78f50 100644 --- a/README.md +++ b/README.md @@ -172,8 +172,9 @@ The following steps will guide you through the process of creating a Kubernetes 1. Follow the previous steps to generate a `furyctl.yaml` and download the modules. 2. Edit the `furyctl.yaml` to customize the cluster configuration by filling the sections `infrastructure`, `kubernetes` and `distribution`. -3. Run `furyctl create cluster` to create the cluster. -4. (Optional) Watch the logs of the cluster creation process with `tail -f ~/.furyctl/furyctl.log`. +3. Check that the configuration file is valid by running `furyctl validate config`. +4. Run `furyctl create cluster` to create the cluster. +5. (Optional) Watch the logs of the cluster creation process with `tail -f ~/.furyctl/furyctl.log`. > 💡 **Alpha ONLY** > @@ -181,9 +182,40 @@ The following steps will guide you through the process of creating a Kubernetes ### Deploy a cluster from an already existing infrastructure -Same as the previous section, but you can skip the `infrastructure` by not filling the section `infrastructure` in the `furyctl.yaml` file and +Same as the previous section, but you can skip the infrastructure creation phase +by not filling the section `infrastructure` in the `furyctl.yaml` file and running `furyctl create cluster --skip-phase infrastructure`. +### Deploy a cluster step by step + +The cluster creation process can be split into three phases: + +- Infrastructure +- Kubernetes +- Distribution + +The `furyctl create cluster` command will execute all the phases by default, +but you can limit the execution to a specific phase by using the flag `--phase`. + +So in order to create a cluster step by step, you can run the following commands: + +```bash +furyctl create cluster --phase infrastructure +``` + +If you choose to create a VPN in the infrastructure phase, you can automatically connect to it by using the flag `--vpn-auto-connect`. + +```bash +furyctl create cluster --phase kubernetes +``` + +After running the command, remember to export the `KUBECONFIG` environment variable to point to the generated kubeconfig file or +to use the flag `--kubeconfig` in the following command. + +```bash +furyctl create cluster --phase distribution +``` + #### Infrastructure The available `infrastructure` provisioners are: From f88530f6faf015b11099afd4832fdd06ec605a4f Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 15:09:46 +0100 Subject: [PATCH 099/383] docs(readme): update readme with furyctl-ng concepts --- README.md | 245 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 163 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index cf6e78f50..ac5be7142 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@

furyctl logo -

The multi-purpose command line tool
for the Kubernetes Fury Distribution

+

The Swiss Army Knife
for the Kubernetes Fury Distribution

-[![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg)](https://ci.sighup.io/sighupio/furyctl) -![Release](https://img.shields.io/github/v/release/sighupio/furyctl?label=Furyctl) + + +[![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg?ref=refs/heads/furyctl-ng-alpha1)](https://ci.sighup.io/sighupio/furyctl) +![Release](https://img.shields.io/badge/Furyctl%20Next%20Generation-alpha1-blue) ![Slack](https://img.shields.io/badge/slack-@kubernetes/fury-yellow.svg?logo=slack) ![License](https://img.shields.io/github/license/sighupio/furyctl) [![Go Report Card](https://goreportcard.com/badge/github.com/sighupio/furyctl)](https://goreportcard.com/report/github.com/sighupio/furyctl) @@ -15,20 +17,73 @@ -Furyctl is a command line interface tool to: - -- create and manage Fury clusters on AWS -- download and manage the Kubernetes Fury Distribution (KFD) modules - +`furyctl` is the command line companion for the Kubernetes Fury Distribution to manage the **full lifecycle** of your Kubernetes Fury clusters.
-![Furyctl usage](docs/assets/furyctl.gif) +> **:warning:** +> you are viewing the readme for furyctl next generation (`ng` for short). This version is in `alpha` status. +> +> `furyctl-ng` supports EKS-based clusters only in the first alpha. + + > 💡 Learn more about the Kubernetes Fury Distribution in the [official site](https://kubernetesfury.com). ## Installation -### Installation from binaries +### Installation from source + +Prerequisites: + +- `make` +- `go>=1.19` +- `goreleaser>=1.11.4` + +To build `furyctl` from source, follow the next steps: + +1. clone the repository: + +```console +git clone git@github.com:sighupio/furyctl.git +# cd into the cloned repository +cd furyctl +# Switch to the branch for the `furyctl-ng-alpha1` version +git switch furyctl-ng-alpha1 +``` + +2. build the binaries by running the following command: + +```console +make build +``` + +3. you will find the binaries for Linux, Darwin (macOS) and Windows for your current architecture in the `dist` folder: + +```console +$ tree dist/furyctl_*/ +dist/furyctl_darwin_amd64_v1 +└── furyctl +dist/furyctl_linux_amd64_v1 +└── furyctl +dist/furyctl_windows_amd64_v1 +└── furyctl.exe +``` + +4. check that the binary is working as expected: + +```console +# replace darwin with your OS and amd64 with your architecture +./dist/furyctl_darwin_amd64_v1/furyctl version +``` + +5. (optional) copy the binary to your `bin` folder: + +```console +# replace darwin with your OS and amd64 with your architecture +sudo mv ./dist/furyctl_darwin_amd64_v1/furyctl /usr/local/bin/furyctl +``` + +### Installation from binaries (not available yet for `furyctl-ng`) You can find `furyctl` binaries on the [Releases page](https://github.com/sighupio/furyctl/releases). @@ -44,18 +99,18 @@ Alternatively, you can install `furyctl` using a brew tap or via an asdf plugin. > ⚠️ M1 users: please download `darwin/amd64` binaries instead of using homebrew or asdf. Even though furyctl can be build for `arm64`, some of its dependendecies are not available yet for this architecture. -### Installation with [Homebrew](https://brew.sh/) +### Installation with [Homebrew](https://brew.sh/) (not available yet for `furyctl-ng`) -```bash +```console brew tap sighupio/furyctl brew install furyctl ``` -### Installation with [asdf](https://github.com/asdf-vm/asdf) +### Installation with [asdf](https://github.com/asdf-vm/asdf) (not available yet for `furyctl-ng`) Add furyctl asdf plugin: -```bash +```console asdf plugin add furyctl ``` @@ -66,52 +121,108 @@ furyctl version INFO[0000] Furyctl version 0.10.0 ``` +## Usage + > 💡 **TIP** > -> Enable autocompletion for `furyctl` CLI on your shell (currently autocompletion is provided for `bash`, `zsh`, `fish`). -> To see the instruction on how to enable it, run `furyctl completion -h` +> Enable command tab autocompletion for `furyctl` on your shell (`bash`, `zsh`, `fish` are supported). +> To see the instruction to enable it, run `furyctl completion -h` -## Usage +See all the available commands with `furyctl help`. + +> ⚠️ **alpha only** +> +> `furyctl-ng` is compatible with KFD versions 1.22.1, 1.23.3 and 1.24.0, but you will need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` +> in _every command_ until the next release of the KFD. -See the available commands with `furyctl --help`: +### Basic Usage -```bash -furyctl --help +Basic usage of `fuyrctl` for a new project consists on: + +1. Creating a configuration file defining the prequired infrastructure, Kubernetes cluster details and KFD modules configuration. +2. Creating a cluster as defined in the configuration file. +3. Destroying the cluster and its related resources. + +#### 1. Create a configuration file -The multi-purpose command line tool for the Kubernetes Fury Distribution. +`furyctl` provides a command that outputs a sample configuration file (by default called `furyctl.yaml`) with all the possible fields commented. +To create a sample configuration file as a starting point use the following command: -Furyctl is a simple CLI tool to: +```console +furyctl create config --version +``` + +> 💡 **TIP** you can pass some additional flags, like the kind of cluster or the configuration file name. See `furyctl create config --help` for more details. + +Open the generated configuration file and edit it according to your needs. You can follow the instructions included as comments in the file. + +furyctl's configuration files have a kind, that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a cluster using the EKS managed clusters from AWS. + +Additionaly, the schema of the file is versioned with the `apiVersion` field, so when new features are introduced you can switch to a newer version of the configuration file structure. + +Once you have completed your configuration file, you can check that it is valid by running the following comand: + +```console +furyctl validate config --config +``` + +> the `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` + +#### 2. Create a cluster + +In the previous step, you have created and validated a configuration file that defines the cluster and its sorroundings, you can now proceed to actually creating the resources. + +furcytl has divided the cluster creation in three phases: `infrastructure`, `kubernetes` and `distribution`. + +- The first phase, `infrastructure`, creates all the prerequisites needed to be able to create a cluster. For example, the VPC and its networks. +- The second phase, `kubernetes`, creates the actual Kubernetes clusters. For example, the EKS cluster with its node pools. +- The third phase, `distribution`, deploys KFD modules to the Kubernetes cluster. + +You may find these phases familiar from editing the configuration file. + +Just like you can validate that your configuration file is well formed. `furyctl` let's you check that you have all the needed dependencies (environment variables, binaries, etc.) before starting a cluster creation process. + +To validate that you have all the dependencies needed to create the cluster defined in your configuration file, run the following command: + +```console +furyctl validate dependencies +``` -- download and manage the Kubernetes Fury Distribution (KFD) modules -- create and manage Kubernetes Fury clusters +Finally, to launch the creation of the resources defined in the configuration file, run the following command: -Usage: - furyctl [command] +> **:warning:** you are about to create cloud resources that could have billing impact. -Available Commands: - completion Generate completion script - create Create a cluster or a config file - delete Delete a cluster - download Download all dependencies from the Kubernetes Fury Distribution specified in the config file - dump Dump templates and other useful fury objects - help Help about any command - validate Validate the config file and the dependencies relative to the Kubernetes Fury Distribution specified in it - version Print the version number of furyctl + -Flags: - -D, --debug Enables furyctl debug output - -d, --disable-analytics Disable analytics - -h, --help help for furyctl - -l, --log string Path to the log file or stdout to log to standard output (default: ~/.furyctl/furyctl.log) - -T, --no-tty Disable TTY - -w, --workdir string Switch to a different working directory before executing the given subcommand. +> **:info:** the creation process you are about to launch can take a while. -Use "furyctl [command] --help" for more information about a command. +```console +furyctl create cluster ``` -## Download and manage KFD modules +🎉 Congratulations! You have created your production-grade Kubernetes Fury Cluster from scratch and its ready to go into battle. + +#### 3. Destroy a cluster + +Destroying a cluster can be thought as running the creation phases in reverse order. `furyctl` automates this operation for you. +To destroy a cluster created using `furyctl` and all its related resources, run the following command: + +> **:warning:** you are about to run a destructive operation. + +```console +furyctl delete cluster --dry-run +``` + +> 💡 **TIP** notice the `--dry-run` flag, used to first check what the command would do. This flag is available for other commands too. + +check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag. + +### Advanced Usage + +#### Download and manage KFD modules + +`furyctl` can be used as a package manager for KFD. -`furyctl` can be used as a package manager for the KFD. It provides a simple way to download all the desired modules of the KFD by reading a single `furyctl.yaml`. The process requires the following steps: @@ -120,13 +231,13 @@ The process requires the following steps: with the flag `--version`. 2. Run `furyctl download dependencies` to download all the dependencies including the modules of the KFD. -### 1. Customize the `furyctl.yaml` +##### 1. Customize the `furyctl.yaml` A `furyctl.yaml` is a YAML formatted file that contains all the information needed to create a Kubernetes Fury cluster. Modules are located in the `distribution` section of the `furyctl.yaml` file and can be configured to better fit your needs. -### 2. Download the modules +##### 2. Download the modules Run `furyctl download dependencies` (within the same directory where your `furyctl.yaml` is located) to download the modules and all the dependencies needed to create a Kubernetes Fury cluster. @@ -136,37 +247,7 @@ needed to create a Kubernetes Fury cluster. > Using the command `furyctl dump template` with the flag `-w` pointing to the local location of the repository `fury-distribution`, > will run the template engine on the modules and generate the final manifests that will be applied to the cluster. -## Cluster creation - -The Cluster creation feature is available via the command `furyctl create cluster`. - -The subcommand accept the following options: - -```bash --b, --bin-path string Path to the bin folder where all dependencies are installed --c, --config string Path to the furyctl.yaml file (default "furyctl.yaml") ---distro-location string Location where to download schemas, defaults and the distribution manifest. It can either be a local path(eg: /path/to/fury/distribution) or a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME). Any format supported by hashicorp/go-getter can be used. ---dry-run Allows to inspect what resources will be created before applying them --h, --help help for cluster ---kubeconfig string Path to the kubeconfig file, mandatory if you want to run the distribution phase and the KUBECONFIG environment variable is not set --p, --phase string Limit the execution to a specific phase. options are: infrastructure, kubernetes, distribution ---skip-deps-download Skip downloading the distribution modules, installers and binaries ---skip-deps-validation Skip validating dependencies ---skip-phase string Avoid executing a unwanted phase. options are: infrastructure, kubernetes, distribution. More specifically: - - skipping infrastructure will execute kubernetes and distribution - - skipping kubernetes will only execute distribution - - skipping distribution will execute infrastructure and kubernetes ---vpn-auto-connect When set will automatically connect to the created VPN in the infrastructure phase - -Global Flags: --D, --debug Enables furyctl debug output --d, --disable-analytics Disable analytics --l, --log string Path to the log file or stdout to log to standard output (default: ~/.furyctl/furyctl.log) --T, --no-tty Disable TTY --w, --workdir string Switch to a different working directory before executing the given subcommand. -``` - -### Deploy a cluster from zero +#### Cluster creation The following steps will guide you through the process of creating a Kubernetes Fury cluster from zero. @@ -197,7 +278,7 @@ The cluster creation process can be split into three phases: The `furyctl create cluster` command will execute all the phases by default, but you can limit the execution to a specific phase by using the flag `--phase`. -So in order to create a cluster step by step, you can run the following commands: +To create a cluster step by step, you can run the following command: ```bash furyctl create cluster --phase infrastructure @@ -239,7 +320,7 @@ The available `kubernetes` provisioners are: Before contributing, please read first the [Contributing Guidelines](docs/CONTRIBUTING.md). -## Test classes +### Test classes There are four kind of tests: unit, integration, e2e, and expensive. @@ -257,10 +338,10 @@ That said, here's a little summary of the used tags: ### Reporting Issues -In case you experience any problems, please [open a new issue](https://github.com/sighupio/furyctl/issues/new/choose). +In case you experience any problems with `furyctl`, please [open a new issue](https://github.com/sighupio/furyctl/issues/new/choose) in GitHub. ## License -This module is open-source and it's released under the following [LICENSE](LICENSE) +This software is open-source and it's released under the following [LICENSE](LICENSE). From f988d46c0ebb975d20429c6ba3ffd115443e6f7e Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 15:10:20 +0100 Subject: [PATCH 100/383] chore: improve commands help messages --- cmd/create.go | 2 +- cmd/delete.go | 2 +- cmd/download.go | 2 +- cmd/dump.go | 2 +- cmd/root.go | 11 ++++------- cmd/validate.go | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/cmd/create.go b/cmd/create.go index 0d0a118a9..9b741517c 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -14,7 +14,7 @@ import ( func NewCreateCommand(tracker *analytics.Tracker) *cobra.Command { createCmd := &cobra.Command{ Use: "create", - Short: "Create a cluster or a config file", + Short: "Create a cluster or a sample configuration file", } createCmd.AddCommand(create.NewClusterCmd(tracker)) diff --git a/cmd/delete.go b/cmd/delete.go index 6af68c6af..78d99a96e 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -14,7 +14,7 @@ import ( func NewDeleteCommand(tracker *analytics.Tracker) *cobra.Command { deleteCmd := &cobra.Command{ Use: "delete", - Short: "Delete a cluster", + Short: "Delete a cluster and its related infrastructure", } deleteCmd.AddCommand(del.NewClusterCmd(tracker)) diff --git a/cmd/download.go b/cmd/download.go index c66bf489b..adba6f223 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -14,7 +14,7 @@ import ( func NewDownloadCmd(tracker *analytics.Tracker) *cobra.Command { dumpCmd := &cobra.Command{ Use: "download", - Short: "Download all dependencies from the Kubernetes Fury Distribution specified in the config file", + Short: "Download all dependencies for the Kubernetes Fury Distribution specified in the configuration file", } dumpCmd.AddCommand(download.NewDependenciesCmd(tracker)) diff --git a/cmd/dump.go b/cmd/dump.go index f77ca2916..f1273a5e1 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -14,7 +14,7 @@ import ( func NewDumpCmd(tracker *analytics.Tracker) *cobra.Command { dumpCmd := &cobra.Command{ Use: "dump", - Short: "Dump templates and other useful fury objects", + Short: "Dump manifests templates and other useful KFD objects", } dumpCmd.AddCommand(dump.NewTemplateCmd(tracker)) diff --git a/cmd/root.go b/cmd/root.go index 0c6e0ccfd..f2067a120 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -58,13 +58,10 @@ func NewRootCommand( rootCmd := &RootCommand{ Command: &cobra.Command{ Use: "furyctl", - Short: "The multi-purpose command line tool for the Kubernetes Fury Distribution", + Short: "The Swiss Army knife for the Kubernetes Fury Distribution", Long: `The multi-purpose command line tool for the Kubernetes Fury Distribution. -Furyctl is a simple CLI tool to: - -- download and manage the Kubernetes Fury Distribution (KFD) modules -- create and manage Kubernetes Fury clusters +furyctl is a command line interface tool to manage the full lifecycle of a Kubernetes Fury Cluster. `, SilenceUsage: true, SilenceErrors: true, @@ -193,14 +190,14 @@ Furyctl is a simple CLI tool to: "workdir", "w", "", - "Switch to a different working directory before executing the given subcommand.", + "Switch to a different working directory before executing the given subcommand", ) rootCmd.PersistentFlags().StringVarP( &rootCmd.config.Log, "log", "l", "", - "Path to the log file or stdout to log to standard output (default: ~/.furyctl/furyctl.log)", + "Path to the log file or set to 'stdout' to log to standard output (default: ~/.furyctl/furyctl.log)", ) rootCmd.AddCommand(NewCompletionCmd(tracker)) diff --git a/cmd/validate.go b/cmd/validate.go index 2c80a0e91..2cc69eed1 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -14,7 +14,7 @@ import ( func NewValidateCommand(tracker *analytics.Tracker) *cobra.Command { validateCmd := &cobra.Command{ Use: "validate", - Short: "Validate the config file and the dependencies relative to the Kubernetes Fury Distribution specified in it", + Short: "Validate a configuration file and the dependencies relative to the Kubernetes Fury Distribution specified in it", } validateCmd.AddCommand(validate.NewConfigCmd(tracker)) From 2584e751ebb3e670944ae615dce45d99453225d7 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 15:25:42 +0100 Subject: [PATCH 101/383] chore: improve help messages and command descriptions --- cmd/create/config.go | 14 +++++++------- cmd/delete/cluster.go | 10 +++++----- cmd/download/dependencies.go | 4 ++-- cmd/root.go | 4 ++-- cmd/validate/config.go | 12 ++++++------ cmd/validate/dependencies.go | 8 ++++---- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cmd/create/config.go b/cmd/create/config.go index 32f513925..539e01f5f 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -29,7 +29,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "config", - Short: "Scaffolds a new furyctl config file", + Short: "Scaffolds a new furyctl configuration file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, @@ -115,12 +115,12 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("failed to create config file: %w", err) + return fmt.Errorf("failed to create configuration file: %w", err) } - logrus.Infof("Config file created successfully at: %s", out.Name()) + logrus.Infof("Configuration file created successfully at: %s", out.Name()) - cmdEvent.AddSuccessMessage(fmt.Sprintf("Config file created successfully at: %s", out.Name())) + cmdEvent.AddSuccessMessage(fmt.Sprintf("Configuration file created successfully at: %s", out.Name())) tracker.Track(cmdEvent) return nil @@ -131,7 +131,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "config", "c", "furyctl.yaml", - "Path to the furyctl.yaml file", + "Path to the configuration file", ) cmd.Flags().StringP( @@ -140,7 +140,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) @@ -162,7 +162,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "api-version", "a", "kfd.sighup.io/v1alpha2", - "Version of the api to use for the selected kind (eg: kfd.sighup.io/v1alpha2)", + "Version of the API to use for the selected kind (eg: kfd.sighup.io/v1alpha2)", ) cmd.Flags().StringP( diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 87f178839..b77ea66d1 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -172,7 +172,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "config", "c", "furyctl.yaml", - "Path to the furyctl.yaml file", + "Path to the configuration file", ) cmd.Flags().StringP( @@ -181,7 +181,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) @@ -196,19 +196,19 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "phase", "p", "", - "Phase to execute", + "Limit execution to the specified phase", ) cmd.Flags().Bool( "dry-run", false, - "Allows to inspect what resources will be deleted", + "when set furyctl won't delete any resources. Allows to inspect what resources will be deleted", ) cmd.Flags().Bool( "force", false, - "Force deletion of the cluster", + "WARNING: furyctl won't ask for confirmation and will force delete the cluster and it resources.", ) cmd.Flags().String( diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 19189f6ee..5857be4ac 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -121,7 +121,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "config", "c", "furyctl.yaml", - "Path to the furyctl.yaml file", + "Path to the configuration file", ) cmd.Flags().StringP( @@ -130,7 +130,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/root.go b/cmd/root.go index f2067a120..14f276f75 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -149,11 +149,11 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber select { case release := <-r: if shouldUpgrade(release.Version, versions["version"]) { - logrus.Infof("New furyctl version available: %s => %s", versions["version"], release.Version) + logrus.Infof("A newer version of furyctl is available: %s => %s", versions["version"], release.Version) } case err := <-e: if err != nil { - logrus.Debugf("Error checking for updates: %s", err) + logrus.Debugf("Error checking for updates to furyctl: %s", err) } } }, diff --git a/cmd/validate/config.go b/cmd/validate/config.go index c72886d65..2088fc435 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -20,7 +20,7 @@ import ( ) var ( - ErrValidationFailed = fmt.Errorf("config validation failed") + ErrValidationFailed = fmt.Errorf("configuration file validation failed") ErrParsingFlag = errors.New("error while parsing flag") ) @@ -29,7 +29,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "config", - Short: "Validate furyctl.yaml file", + Short: "Validate configuration file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, @@ -71,9 +71,9 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { return ErrValidationFailed } - logrus.Info("config validation succeeded") + logrus.Info("configuration file validation succeeded") - cmdEvent.AddSuccessMessage("config validation succeeded") + cmdEvent.AddSuccessMessage("configuration file validation succeeded") tracker.Track(cmdEvent) return nil @@ -84,7 +84,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "config", "c", "furyctl.yaml", - "Path to the furyctl.yaml file", + "Path to the configuration file", ) cmd.Flags().StringP( @@ -93,7 +93,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 3741591af..903bd02a1 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -29,7 +29,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", - Short: "Validate dependencies from the Kubernetes Fury Distribution specified in the config file", + Short: "Validate dependencies for the Kubernetes Fury Distribution specified in the configuration file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, @@ -107,7 +107,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { logrus.Info( "You can use the 'furyctl download dependencies' command to download most dependencies, " + - "and a package manager such as 'asdf' to install the other ones.", + "and a package manager such as 'asdf' to install the remaining ones.", ) return ErrDependencies @@ -133,7 +133,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "config", "c", "furyctl.yaml", - "Path to the furyctl.yaml file", + "Path to the configuration file", ) cmd.Flags().StringP( @@ -142,7 +142,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "", "Base URL used to download schemas, defaults and the distribution manifest. "+ "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1)."+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ "Any format supported by hashicorp/go-getter can be used.", ) From 758776fcc92937a310b3ac086668f6ac93ff806a Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 15:30:52 +0100 Subject: [PATCH 102/383] docs(readme): fix admonitions --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac5be7142..1676e771a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,12 @@ > 💡 Learn more about the Kubernetes Fury Distribution in the [official site](https://kubernetesfury.com). + + +> **Warning** +> You are viewing the readme for furyctl next generation (`furyctl-ng` for short). This version is in `alpha` status. +> `furyctl-ng` supports EKS-based clusters in the first alpha. + ## Installation ### Installation from source @@ -207,7 +213,7 @@ furyctl create cluster Destroying a cluster can be thought as running the creation phases in reverse order. `furyctl` automates this operation for you. To destroy a cluster created using `furyctl` and all its related resources, run the following command: -> **:warning:** you are about to run a destructive operation. +> **Warning** you are about to run a destructive operation. ```console furyctl delete cluster --dry-run From 353e0d8147d77257bd1704a60db24fd2bf8c9e3c Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 15:41:03 +0100 Subject: [PATCH 103/383] docs(readme): first round of improvements --- README.md | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1676e771a..7942d506e 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ > **Warning** > You are viewing the readme for furyctl next generation (`furyctl-ng` for short). This version is in `alpha` status. +> > `furyctl-ng` supports EKS-based clusters in the first alpha. ## Installation @@ -132,20 +133,21 @@ INFO[0000] Furyctl version 0.10.0 > 💡 **TIP** > > Enable command tab autocompletion for `furyctl` on your shell (`bash`, `zsh`, `fish` are supported). -> To see the instruction to enable it, run `furyctl completion -h` +> See the instruction on how to enable it with `furyctl completion --help` See all the available commands with `furyctl help`. -> ⚠️ **alpha only** +> **Warning** +> furyctl-ng alpha only > > `furyctl-ng` is compatible with KFD versions 1.22.1, 1.23.3 and 1.24.0, but you will need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` > in _every command_ until the next release of the KFD. ### Basic Usage -Basic usage of `fuyrctl` for a new project consists on: +Basic usage of `fuyrctl` for a new project consists on the following steps: -1. Creating a configuration file defining the prequired infrastructure, Kubernetes cluster details and KFD modules configuration. +1. Creating a configuration file defining the prequired infrastructure, Kubernetes cluster details, and KFD modules configuration. 2. Creating a cluster as defined in the configuration file. 3. Destroying the cluster and its related resources. @@ -158,21 +160,26 @@ To create a sample configuration file as a starting point use the following comm furyctl create config --version ``` -> 💡 **TIP** you can pass some additional flags, like the kind of cluster or the configuration file name. See `furyctl create config --help` for more details. +> 💡 **TIP** +> +> You can pass some additional flags, like the kind of cluster or the configuration file name. +> +> See `furyctl create config --help` for more details. Open the generated configuration file and edit it according to your needs. You can follow the instructions included as comments in the file. -furyctl's configuration files have a kind, that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a cluster using the EKS managed clusters from AWS. +furyctl's configuration files have a kind, that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a KFD cluster using the EKS managed clusters from AWS. Additionaly, the schema of the file is versioned with the `apiVersion` field, so when new features are introduced you can switch to a newer version of the configuration file structure. -Once you have completed your configuration file, you can check that it is valid by running the following comand: +Once you have filled your configuration file, you can check that it's content is valid by running the following comand: ```console furyctl validate config --config ``` -> the `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` +> **Note** +> The `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` #### 2. Create a cluster @@ -184,7 +191,7 @@ furcytl has divided the cluster creation in three phases: `infrastructure`, `kub - The second phase, `kubernetes`, creates the actual Kubernetes clusters. For example, the EKS cluster with its node pools. - The third phase, `distribution`, deploys KFD modules to the Kubernetes cluster. -You may find these phases familiar from editing the configuration file. +> 💡 You may find these phases familiar from editing the configuration file. Just like you can validate that your configuration file is well formed. `furyctl` let's you check that you have all the needed dependencies (environment variables, binaries, etc.) before starting a cluster creation process. @@ -206,7 +213,7 @@ Finally, to launch the creation of the resources defined in the configuration fi furyctl create cluster ``` -🎉 Congratulations! You have created your production-grade Kubernetes Fury Cluster from scratch and its ready to go into battle. +🎉 Congratulations! You have created your production-grade Kubernetes Fury Cluster from scratch and it's ready to go into battle. #### 3. Destroy a cluster @@ -219,9 +226,11 @@ To destroy a cluster created using `furyctl` and all its related resources, run furyctl delete cluster --dry-run ``` -> 💡 **TIP** notice the `--dry-run` flag, used to first check what the command would do. This flag is available for other commands too. +> 💡 **TIP** +> +> Notice the `--dry-run` flag, used to first check what the command would do. This flag is available for other commands too. -check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag. +check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag to actually delete all the resources. ### Advanced Usage From 86486f50395ceb32b8006914c764e30b85d4fef3 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 16:15:36 +0100 Subject: [PATCH 104/383] dcos(readme): fix headings levels, minor tweaks --- README.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7942d506e..15a4fd5a3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg?ref=refs/heads/furyctl-ng-alpha1)](https://ci.sighup.io/sighupio/furyctl) -![Release](https://img.shields.io/badge/Furyctl%20Next%20Generation-alpha1-blue) +![Release](https://img.shields.io/badge/furyctl%20Next%20Generation-alpha1-blue) ![Slack](https://img.shields.io/badge/slack-@kubernetes/fury-yellow.svg?logo=slack) ![License](https://img.shields.io/github/license/sighupio/furyctl) [![Go Report Card](https://goreportcard.com/badge/github.com/sighupio/furyctl)](https://goreportcard.com/report/github.com/sighupio/furyctl) @@ -34,7 +34,7 @@ > **Warning** > You are viewing the readme for furyctl next generation (`furyctl-ng` for short). This version is in `alpha` status. > -> `furyctl-ng` supports EKS-based clusters in the first alpha. +> `furyctl-ng` currently has support for EKS-based clusters only. ## Installation @@ -43,13 +43,15 @@ Prerequisites: - `make` -- `go>=1.19` -- `goreleaser>=1.11.4` +- `go >= 1.19` +- `goreleaser >= 1.11.4` To build `furyctl` from source, follow the next steps: 1. clone the repository: + + ```console git clone git@github.com:sighupio/furyctl.git # cd into the cloned repository @@ -130,12 +132,14 @@ INFO[0000] Furyctl version 0.10.0 ## Usage +See all the available commands and their usage by running `furyctl help`. + > 💡 **TIP** > > Enable command tab autocompletion for `furyctl` on your shell (`bash`, `zsh`, `fish` are supported). > See the instruction on how to enable it with `furyctl completion --help` -See all the available commands with `furyctl help`. + > **Warning** > furyctl-ng alpha only @@ -178,8 +182,7 @@ Once you have filled your configuration file, you can check that it's content is furyctl validate config --config ``` -> **Note** -> The `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` +> **Note** the `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` #### 2. Create a cluster @@ -213,6 +216,8 @@ Finally, to launch the creation of the resources defined in the configuration fi furyctl create cluster ``` +> **Note** the creation process can take a while. + 🎉 Congratulations! You have created your production-grade Kubernetes Fury Cluster from scratch and it's ready to go into battle. #### 3. Destroy a cluster @@ -226,12 +231,12 @@ To destroy a cluster created using `furyctl` and all its related resources, run furyctl delete cluster --dry-run ``` +check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag to actually delete all the resources. + > 💡 **TIP** > > Notice the `--dry-run` flag, used to first check what the command would do. This flag is available for other commands too. -check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag to actually delete all the resources. - ### Advanced Usage #### Download and manage KFD modules @@ -282,7 +287,7 @@ Same as the previous section, but you can skip the infrastructure creation phase by not filling the section `infrastructure` in the `furyctl.yaml` file and running `furyctl create cluster --skip-phase infrastructure`. -### Deploy a cluster step by step +#### Deploy a cluster step by step The cluster creation process can be split into three phases: @@ -312,7 +317,7 @@ to use the flag `--kubeconfig` in the following command. furyctl create cluster --phase distribution ``` -#### Infrastructure +##### Infrastructure The available `infrastructure` provisioners are: @@ -320,7 +325,7 @@ The available `infrastructure` provisioners are: | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `aws` | It creates a VPC with all the requirements to deploy a Kubernetes Cluster. It also includes a VPN instance easily manageable by using `furyagent`. | -#### Kubernetes +##### Kubernetes The available `kubernetes` provisioners are: From c3fcad8613b7532baf61b7d9c167c8d31d075b0a Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 16:28:24 +0100 Subject: [PATCH 105/383] fix: e2e test validation --- test/e2e/furyctl_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 1e32e8ff7..b594dfa9c 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -171,14 +171,14 @@ var ( out, err := FuryctlValidateConfig("../data/e2e/validate/config/wrong") Expect(err).To(HaveOccurred()) - Expect(out).To(ContainSubstring("config validation failed")) + Expect(out).To(ContainSubstring("configuration validation failed")) }) It("should exit without errors when config validation succeeds", func() { out, err := FuryctlValidateConfig("../data/e2e/validate/config/correct") Expect(err).To(Not(HaveOccurred())) - Expect(out).To(ContainSubstring("config validation succeeded")) + Expect(out).To(ContainSubstring("configuration validation succeeded")) }) }) From 57a14eb33cb85ea345d57f99c92155ec43c596c3 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 16:32:00 +0100 Subject: [PATCH 106/383] chore: improve disable TTY help message --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 14f276f75..cfcaefa35 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -183,7 +183,7 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber "no-tty", "T", false, - "Disable TTY", + "Disable TTY making furyctl's output more friendly to non-interactive shells by disabling animations and colors", ) rootCmd.PersistentFlags().StringVarP( &rootCmd.config.Workdir, From 0cca35d4584ff70eac7c553e1bebbadcffec3234 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 16:33:22 +0100 Subject: [PATCH 107/383] fix: e2e test validation pt.2 --- test/e2e/furyctl_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index b594dfa9c..35816263b 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -171,14 +171,14 @@ var ( out, err := FuryctlValidateConfig("../data/e2e/validate/config/wrong") Expect(err).To(HaveOccurred()) - Expect(out).To(ContainSubstring("configuration validation failed")) + Expect(out).To(ContainSubstring("configuration file validation failed")) }) It("should exit without errors when config validation succeeds", func() { out, err := FuryctlValidateConfig("../data/e2e/validate/config/correct") Expect(err).To(Not(HaveOccurred())) - Expect(out).To(ContainSubstring("configuration validation succeeded")) + Expect(out).To(ContainSubstring("configuration file validation succeeded")) }) }) From d0fe40fa3511131201e5a5958c0f424cb9b91ac3 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Mon, 23 Jan 2023 17:05:45 +0100 Subject: [PATCH 108/383] docs(readme): fix build dependencies versions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15a4fd5a3..e144bbbbc 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ Prerequisites: - `make` -- `go >= 1.19` -- `goreleaser >= 1.11.4` +- `go == v1.19` +- `goreleaser == v1.11.4` To build `furyctl` from source, follow the next steps: From c5a6f64410cf276cf3ea10760b09673d49d2242f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 23 Jan 2023 18:20:00 +0100 Subject: [PATCH 109/383] feat: added info to user when running dry-run without phase --- cmd/delete/cluster.go | 3 ++- internal/apis/kfd/v1alpha2/eks/creator.go | 17 +++++++++++++---- internal/apis/kfd/v1alpha2/eks/deleter.go | 21 +++++++++++++++++---- internal/cluster/deleter.go | 8 +++++++- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index b77ea66d1..b895465ac 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -119,6 +119,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { basePath, flags.BinPath, flags.Kubeconfig, + flags.DryRun, ) if err != nil { cmdEvent.AddErrorMessage(err) @@ -149,7 +150,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { } } - err = clusterDeleter.Delete(flags.DryRun) + err = clusterDeleter.Delete() if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 8080f7667..9499b57ea 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -166,6 +166,11 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseAll: + if v.dryRun { + logrus.Info("furcytl will try its best to calculate what would have changed. " + + "Sometimes this is not possible, for better results limit the scope with the --phase flag.") + } + if v.furyctlConf.Spec.Infrastructure != nil && (skipPhase == "" || skipPhase == cluster.OperationPhaseDistribution) { if err := infra.Exec(infraOpts); err != nil { @@ -178,8 +183,10 @@ func (v *ClusterCreator) Create(skipPhase string) error { return fmt.Errorf("error while executing kubernetes phase: %w", err) } - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } } } @@ -188,8 +195,10 @@ func (v *ClusterCreator) Create(skipPhase string) error { return fmt.Errorf("error while executing distribution phase: %w", err) } - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } } } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 0fca39e4f..bdc3479d5 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -8,6 +8,8 @@ import ( "fmt" "strings" + "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/config" del "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/delete" "github.com/sighupio/furyctl/internal/cluster" @@ -19,6 +21,7 @@ type ClusterDeleter struct { workDir string binPath string kubeconfig string + dryRun bool } func (d *ClusterDeleter) SetProperties(props []cluster.DeleterProperty) { @@ -55,21 +58,26 @@ func (d *ClusterDeleter) SetProperty(name string, value any) { if s, ok := value.(string); ok { d.kubeconfig = s } + + case cluster.DeleterPropertyDryRun: + if b, ok := value.(bool); ok { + d.dryRun = b + } } } -func (d *ClusterDeleter) Delete(dryRun bool) error { - distro, err := del.NewDistribution(dryRun, d.workDir, d.binPath, d.kfdManifest, d.kubeconfig) +func (d *ClusterDeleter) Delete() error { + distro, err := del.NewDistribution(d.dryRun, d.workDir, d.binPath, d.kfdManifest, d.kubeconfig) if err != nil { return fmt.Errorf("error while creating distribution phase: %w", err) } - kube, err := del.NewKubernetes(dryRun, d.workDir, d.binPath, d.kfdManifest) + kube, err := del.NewKubernetes(d.dryRun, d.workDir, d.binPath, d.kfdManifest) if err != nil { return fmt.Errorf("error while creating kubernetes phase: %w", err) } - infra, err := del.NewInfrastructure(dryRun, d.workDir, d.binPath, d.kfdManifest) + infra, err := del.NewInfrastructure(d.dryRun, d.workDir, d.binPath, d.kfdManifest) if err != nil { return fmt.Errorf("error while creating infrastructure phase: %w", err) } @@ -97,6 +105,11 @@ func (d *ClusterDeleter) Delete(dryRun bool) error { return nil case cluster.OperationPhaseAll: + if d.dryRun { + logrus.Info("furcytl will try its best to calculate what would have changed. " + + "Sometimes this is not possible, for better results limit the scope with the --phase flag.") + } + if err := distro.Exec(); err != nil { return fmt.Errorf("error while deleting distribution phase: %w", err) } diff --git a/internal/cluster/deleter.go b/internal/cluster/deleter.go index 5891065d0..fd62f9d05 100644 --- a/internal/cluster/deleter.go +++ b/internal/cluster/deleter.go @@ -17,6 +17,7 @@ const ( DeleterPropertyKfdManifest = "kfdmanifest" DeleterPropertyBinPath = "binpath" DeleterPropertyKubeconfig = "kubeconfig" + DeleterPropertyDryRun = "dryrun" ) var delFactories = make(map[string]map[string]DeleterFactory) //nolint:gochecknoglobals, lll // This patterns requires factories @@ -32,7 +33,7 @@ type DeleterProperty struct { type Deleter interface { SetProperties(props []DeleterProperty) SetProperty(name string, value any) - Delete(dryRun bool) error + Delete() error } func NewDeleter( @@ -42,6 +43,7 @@ func NewDeleter( workDir, binPath, kubeconfig string, + dryRun bool, ) (Deleter, error) { lcAPIVersion := strings.ToLower(minimalConf.APIVersion) lcResourceType := strings.ToLower(minimalConf.Kind) @@ -68,6 +70,10 @@ func NewDeleter( Name: DeleterPropertyKubeconfig, Value: kubeconfig, }, + { + Name: DeleterPropertyDryRun, + Value: dryRun, + }, }) } From 530629aa9083401edaf3e118939f128c330cd094 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Tue, 24 Jan 2023 15:43:19 +0100 Subject: [PATCH 110/383] docs(readme): minor tweaks, fix typos and formatting --- README.md | 60 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e144bbbbc..195e3bc16 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ > **Warning** > You are viewing the readme for furyctl next generation (`furyctl-ng` for short). This version is in `alpha` status. > -> `furyctl-ng` currently has support for EKS-based clusters only. +> `furyctl-ng` is in `alpha` status and currently supports EKS-based clusters only. ## Installation @@ -46,9 +46,11 @@ Prerequisites: - `go == v1.19` - `goreleaser == v1.11.4` -To build `furyctl` from source, follow the next steps: +> You can install `goreleaser` with the following command once you have Go in your system: `go install github.com/goreleaser/goreleaser@v1.11.4` -1. clone the repository: +To install `furyctl` from source, follow the next steps: + +1. Clone the repository: @@ -60,13 +62,13 @@ cd furyctl git switch furyctl-ng-alpha1 ``` -2. build the binaries by running the following command: +2. Build the binaries by running the following command: ```console make build ``` -3. you will find the binaries for Linux, Darwin (macOS) and Windows for your current architecture in the `dist` folder: +3. You will find the binaries for Linux, Darwin (macOS) and Windows for your current architecture inside the `dist` folder: ```console $ tree dist/furyctl_*/ @@ -78,17 +80,17 @@ dist/furyctl_windows_amd64_v1 └── furyctl.exe ``` -4. check that the binary is working as expected: +4. Check that the binary is working as expected: + +> **Note** replace darwin with your OS and amd64 with your architecture in the following commands. ```console -# replace darwin with your OS and amd64 with your architecture ./dist/furyctl_darwin_amd64_v1/furyctl version ``` 5. (optional) copy the binary to your `bin` folder: ```console -# replace darwin with your OS and amd64 with your architecture sudo mv ./dist/furyctl_darwin_amd64_v1/furyctl /usr/local/bin/furyctl ``` @@ -142,14 +144,14 @@ See all the available commands and their usage by running `furyctl help`. > **Warning** -> furyctl-ng alpha only +> (furyctl-ng alpha version only) > > `furyctl-ng` is compatible with KFD versions 1.22.1, 1.23.3 and 1.24.0, but you will need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` > in _every command_ until the next release of the KFD. ### Basic Usage -Basic usage of `fuyrctl` for a new project consists on the following steps: +Basic usage of `furyctl` for a new project consists on the following steps: 1. Creating a configuration file defining the prequired infrastructure, Kubernetes cluster details, and KFD modules configuration. 2. Creating a cluster as defined in the configuration file. @@ -157,7 +159,12 @@ Basic usage of `fuyrctl` for a new project consists on the following steps: #### 1. Create a configuration file -`furyctl` provides a command that outputs a sample configuration file (by default called `furyctl.yaml`) with all the possible fields commented. +`furyctl` provides a command that outputs a sample configuration file (by default called `furyctl.yaml`) with all the possible fields explained in comments. + +furyctl configuration files have a kind, that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a KFD cluster using the EKS managed clusters from AWS. + +Additionaly, the schema of the file is versioned with the `apiVersion` field, so when new features are introduced you can switch to a newer version of the configuration file structure. + To create a sample configuration file as a starting point use the following command: ```console @@ -166,15 +173,11 @@ furyctl create config --version > 💡 **TIP** > -> You can pass some additional flags, like the kind of cluster or the configuration file name. +> You can pass some additional flags, like the schema (API) version of the configuration file or a different configuration file name. > > See `furyctl create config --help` for more details. -Open the generated configuration file and edit it according to your needs. You can follow the instructions included as comments in the file. - -furyctl's configuration files have a kind, that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a KFD cluster using the EKS managed clusters from AWS. - -Additionaly, the schema of the file is versioned with the `apiVersion` field, so when new features are introduced you can switch to a newer version of the configuration file structure. +Open the generated configuration file with your editor of choice (`vi`) and edit it according to your needs. You can follow the instructions included as comments in the file. Once you have filled your configuration file, you can check that it's content is valid by running the following comand: @@ -186,19 +189,19 @@ furyctl validate config --config #### 2. Create a cluster -In the previous step, you have created and validated a configuration file that defines the cluster and its sorroundings, you can now proceed to actually creating the resources. +In the previous step, you have created and validated a configuration file that defines the Kubernetes cluster and its sorroundings, you can now proceed to actually creating the resources. -furcytl has divided the cluster creation in three phases: `infrastructure`, `kubernetes` and `distribution`. +furyctl has divided the cluster creation in three phases: `infrastructure`, `kubernetes` and `distribution`. -- The first phase, `infrastructure`, creates all the prerequisites needed to be able to create a cluster. For example, the VPC and its networks. -- The second phase, `kubernetes`, creates the actual Kubernetes clusters. For example, the EKS cluster with its node pools. -- The third phase, `distribution`, deploys KFD modules to the Kubernetes cluster. +1. The first phase, `infrastructure`, creates all the prerequisites needed to be able to create a cluster. For example, the VPC and its networks. +2. The second phase, `kubernetes`, creates the actual Kubernetes clusters. For example, the EKS cluster and its node pools. +3. The third phase, `distribution`, deploys KFD modules to the Kubernetes cluster. > 💡 You may find these phases familiar from editing the configuration file. -Just like you can validate that your configuration file is well formed. `furyctl` let's you check that you have all the needed dependencies (environment variables, binaries, etc.) before starting a cluster creation process. +Just like you can validate that your configuration file is well formed, `furyctl` let's you check that you have all the needed dependencies (environment variables, binaries, etc.) before starting a cluster creation process. -To validate that you have all the dependencies needed to create the cluster defined in your configuration file, run the following command: +To validate that your system has all the dependencies needed to create the cluster defined in your configuration file, run the following command: ```console furyctl validate dependencies @@ -223,6 +226,7 @@ furyctl create cluster #### 3. Destroy a cluster Destroying a cluster can be thought as running the creation phases in reverse order. `furyctl` automates this operation for you. + To destroy a cluster created using `furyctl` and all its related resources, run the following command: > **Warning** you are about to run a destructive operation. @@ -262,7 +266,7 @@ Modules are located in the `distribution` section of the `furyctl.yaml` file and Run `furyctl download dependencies` (within the same directory where your `furyctl.yaml` is located) to download the modules and all the dependencies needed to create a Kubernetes Fury cluster. -> 🔥 **Advanced User** +> 🔥 **Advanced Tip** > > Using the command `furyctl dump template` with the flag `-w` pointing to the local location of the repository `fury-distribution`, > will run the template engine on the modules and generate the final manifests that will be applied to the cluster. @@ -291,9 +295,9 @@ running `furyctl create cluster --skip-phase infrastructure`. The cluster creation process can be split into three phases: -- Infrastructure -- Kubernetes -- Distribution +1. Infrastructure +2. Kubernetes +3. Distribution The `furyctl create cluster` command will execute all the phases by default, but you can limit the execution to a specific phase by using the flag `--phase`. From 809d38db23bd0c1f83e69d3649006cc7ad7f63ea Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Tue, 24 Jan 2023 15:50:43 +0100 Subject: [PATCH 111/383] docs(readme): improve copy --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 195e3bc16..12392926d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,11 @@ Prerequisites: - `go == v1.19` - `goreleaser == v1.11.4` -> You can install `goreleaser` with the following command once you have Go in your system: `go install github.com/goreleaser/goreleaser@v1.11.4` +> You can install `goreleaser` with the following command once you have Go in your system: +> +> ```console +> go install github.com/goreleaser/goreleaser@v1.11.4 +> ``` To install `furyctl` from source, follow the next steps: @@ -82,13 +86,13 @@ dist/furyctl_windows_amd64_v1 4. Check that the binary is working as expected: -> **Note** replace darwin with your OS and amd64 with your architecture in the following commands. +> **Note** replace `darwin` with your OS and `amd64` with your architecture in the following commands. ```console ./dist/furyctl_darwin_amd64_v1/furyctl version ``` -5. (optional) copy the binary to your `bin` folder: +5. (optional) move the binary to your `bin` folder, in macOS: ```console sudo mv ./dist/furyctl_darwin_amd64_v1/furyctl /usr/local/bin/furyctl @@ -177,7 +181,7 @@ furyctl create config --version > > See `furyctl create config --help` for more details. -Open the generated configuration file with your editor of choice (`vi`) and edit it according to your needs. You can follow the instructions included as comments in the file. +Open the generated configuration file with your editor of choice and edit it according to your needs. You can follow the instructions included as comments in the file. Once you have filled your configuration file, you can check that it's content is valid by running the following comand: @@ -239,7 +243,7 @@ check that the dry-run output is what you expected and then run the command agai > 💡 **TIP** > -> Notice the `--dry-run` flag, used to first check what the command would do. This flag is available for other commands too. +> Notice the `--dry-run` flag, used to check what the command would do. This flag is available for other commands too. ### Advanced Usage From 75711a145e8f979f7e8f9b44dc1b9d6d2c6dc8cd Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Tue, 24 Jan 2023 16:36:08 +0100 Subject: [PATCH 112/383] chore: improve logging for configuration file --- cmd/create/cluster.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 77cf96599..93eb2b613 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -125,12 +125,12 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { depsvl := dependencies.NewValidator(executor, flags.BinPath) // Validate the furyctl.yaml file. - logrus.Info("Validating furyctl.yaml file...") + logrus.Info("Validating configuration file...") if err := config.Validate(flags.FuryctlPath, res.RepoPath); err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("error while validating furyctl.yaml file: %w", err) + return fmt.Errorf("error while validating configuration file: %w", err) } // Download the dependencies. @@ -310,7 +310,7 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "config", "c", "furyctl.yaml", - "Path to the furyctl.yaml file", + "Path to the configuration file", ) cmd.Flags().StringP( From c7dc55e85b186ce90586cdf96d1ce7d712dbcee4 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Wed, 25 Jan 2023 16:46:29 +0100 Subject: [PATCH 113/383] docs(readme): add --distro-location to all commands - add distro-location flag to all commands - remove provisioners and installers tables - add VPN note before the create command usage example --- .goreleaser.yml | 4 ++-- README.md | 32 ++++++++------------------------ 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 87be93269..d99069b4c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -28,7 +28,7 @@ archives: checksum: name_template: 'checksums.txt' snapshot: - name_template: "{{ incpatch .Version }}-next" + name_template: "{{ incpatch .Version }}-ng" changelog: sort: asc filters: @@ -49,7 +49,7 @@ brews: skip_upload: auto folder: Formula homepage: 'https://gihub.com/sighupio/furyctl' - description: 'Furyctl binary' + description: 'furyctl binary' conflicts: - furyctl test: | diff --git a/README.md b/README.md index 12392926d..8676fa116 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Prerequisites: - `go == v1.19` - `goreleaser == v1.11.4` -> You can install `goreleaser` with the following command once you have Go in your system: +> You can install `goreleaser` with the following command once you have Go in your system: > > ```console > go install github.com/goreleaser/goreleaser@v1.11.4 @@ -186,7 +186,7 @@ Open the generated configuration file with your editor of choice and edit it acc Once you have filled your configuration file, you can check that it's content is valid by running the following comand: ```console -furyctl validate config --config +furyctl validate config --config --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' ``` > **Note** the `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` @@ -208,7 +208,7 @@ Just like you can validate that your configuration file is well formed, `furyctl To validate that your system has all the dependencies needed to create the cluster defined in your configuration file, run the following command: ```console -furyctl validate dependencies +furyctl validate dependencies --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' ``` Finally, to launch the creation of the resources defined in the configuration file, run the following command: @@ -220,7 +220,7 @@ Finally, to launch the creation of the resources defined in the configuration fi > **:info:** the creation process you are about to launch can take a while. ```console -furyctl create cluster +furyctl create cluster --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' ``` > **Note** the creation process can take a while. @@ -236,7 +236,7 @@ To destroy a cluster created using `furyctl` and all its related resources, run > **Warning** you are about to run a destructive operation. ```console -furyctl delete cluster --dry-run +furyctl delete cluster --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' --dry-run ``` check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag to actually delete all the resources. @@ -309,38 +309,22 @@ but you can limit the execution to a specific phase by using the flag `--phase`. To create a cluster step by step, you can run the following command: ```bash -furyctl create cluster --phase infrastructure +furyctl create cluster --phase infrastructure --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' ``` If you choose to create a VPN in the infrastructure phase, you can automatically connect to it by using the flag `--vpn-auto-connect`. ```bash -furyctl create cluster --phase kubernetes +furyctl create cluster --phase kubernetes --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' ``` After running the command, remember to export the `KUBECONFIG` environment variable to point to the generated kubeconfig file or to use the flag `--kubeconfig` in the following command. ```bash -furyctl create cluster --phase distribution +furyctl create cluster --phase distribution --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' ``` -##### Infrastructure - -The available `infrastructure` provisioners are: - -| Provisioner | Description | -| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `aws` | It creates a VPC with all the requirements to deploy a Kubernetes Cluster. It also includes a VPN instance easily manageable by using `furyagent`. | - -##### Kubernetes - -The available `kubernetes` provisioners are: - -| Provisioner | Description | -| ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `eks` | Creates an EKS cluster on an already existing VPC. It uses the [fury-eks-installer](https://github.com/sighupio/fury-eks-installer) | - From e35d0eac1158d3b43bd6368fe02a964c6bf9cf69 Mon Sep 17 00:00:00 2001 From: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:24:02 +0100 Subject: [PATCH 114/383] build: add amr664 arch when create release packages (#202) --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index d99069b4c..4e8ff3b12 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,6 +16,7 @@ builds: - darwin goarch: - amd64 + - arm64 ldflags: - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} archives: From 29d7381171ffa73fb35c20469741ffe98ae5224b Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:12:45 +0100 Subject: [PATCH 115/383] Feat: point source to local folder instead of remote (#218) * feat: point source to local folder instead of remote * feat: added conditional experiments --- .../provisioners/bootstrap/aws/main.tf.tpl | 32 +++++++++++++++++++ configs/provisioners/cluster/eks/main.tf.tpl | 8 +++-- .../kfd/v1alpha2/eks/create/infrastructure.go | 4 ++- .../kfd/v1alpha2/eks/create/kubernetes.go | 6 +++- 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 configs/provisioners/bootstrap/aws/main.tf.tpl diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl new file mode 100644 index 000000000..dd1da4c61 --- /dev/null +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +terraform { + backend "s3" { + bucket = "{{ .terraform.backend.s3.bucketName }}" + key = "{{ .terraform.backend.s3.keyPrefix }}/infrastructure.json" + region = "{{ .terraform.backend.s3.region }}" + } +} + +module "vpc-and-vpn" { + source = "{{ .kubernetes.installerPath }}" + + name = var.name + network_cidr = var.network_cidr + public_subnetwork_cidrs = var.public_subnetwork_cidrs + private_subnetwork_cidrs = var.private_subnetwork_cidrs + vpn_subnetwork_cidr = var.vpn_subnetwork_cidr + vpn_port = var.vpn_port + vpn_instances = var.vpn_instances + vpn_instance_type = var.vpn_instance_type + vpn_instance_disk_size = var.vpn_instance_disk_size + vpn_operator_name = var.vpn_operator_name + vpn_dhparams_bits = var.vpn_dhparams_bits + vpn_operator_cidrs = var.vpn_operator_cidrs + vpn_ssh_users = var.vpn_ssh_users + tags = var.tags +} diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index fa43e46c3..31d9ed561 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -4,8 +4,12 @@ * license that can be found in the LICENSE file. */ +{{- $deprecateOptionalTfVer := semver "1.3.0" }} + terraform { - experiments = [module_variable_optional_attrs] + {{ if eq ($deprecateOptionalTfVer | (semver .kubernetes.tfVersion).Compare) -1 -}} + experiments = [module_variable_optional_attrs] + {{ end -}} backend "s3" { bucket = "{{ .terraform.backend.s3.bucketName }}" @@ -15,7 +19,7 @@ terraform { } module "fury" { - source = "github.com/sighupio/fury-eks-installer//modules/eks?ref={{ .kubernetes.eks.installer }}" + source = "{{ .kubernetes.installerPath }}" cluster_name = var.cluster_name cluster_version = var.cluster_version diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 4d09171dd..22d19d010 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -211,9 +211,11 @@ func (i *Infrastructure) copyFromTemplate() error { targetTfDir := path.Join(i.Path, "terraform") prefix := "infra" + eksInstallerPath := path.Join(i.Path, "..", "vendor", "installers", "eks", "modules", "vpc-and-vpn") + cfg.Data = map[string]map[any]any{ "kubernetes": { - "eks": i.kfdManifest.Kubernetes.Eks, + "installerPath": eksInstallerPath, }, "terraform": { "backend": map[string]any{ diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 4d3a93b71..2736c48c2 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -172,9 +172,13 @@ func (k *Kubernetes) copyFromTemplate() error { targetTfDir := path.Join(k.Path, "terraform") prefix := "kube" + + eksInstallerPath := path.Join(k.Path, "..", "vendor", "installers", "eks", "modules", "eks") + tfConfVars := map[string]map[any]any{ "kubernetes": { - "eks": k.kfdManifest.Kubernetes.Eks, + "installerPath": eksInstallerPath, + "tfVersion": k.kfdManifest.Tools.Common.Terraform.Version, }, "terraform": { "backend": map[string]any{ From bb3d1b12f736eb4e71867b3fafcb8dd2265a725b Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Fri, 10 Feb 2023 16:26:36 +0100 Subject: [PATCH 116/383] docs(readme): use temporay png for logo --- README.md | 4 +++- docs/assets/furyctl-temporary.png | Bin 0 -> 14121 bytes docs/assets/furyctl.gif | Bin 296743 -> 0 bytes 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/assets/furyctl-temporary.png delete mode 100644 docs/assets/furyctl.gif diff --git a/README.md b/README.md index 8676fa116..a1d4fc025 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@

- furyctl logo + + + furyctl logo

The Swiss Army Knife
for the Kubernetes Fury Distribution

diff --git a/docs/assets/furyctl-temporary.png b/docs/assets/furyctl-temporary.png new file mode 100644 index 0000000000000000000000000000000000000000..f7bca492c65946ca7c7f4b1c12dd0bddbc5baaad GIT binary patch literal 14121 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 literal 0 HcmV?d00001 diff --git a/docs/assets/furyctl.gif b/docs/assets/furyctl.gif deleted file mode 100644 index 9b6dda9444b918d57cb82a366c69d975b4a40851..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 296743 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 z zizzxdC^-ne54^!7EWrS5F}xD4D?F|(%)&2>OZ`NzhUbA3A_9b4yQuP;;i(FbJ0afb zjX0tPX2^j_S$-5mwrhaHnWzLw@F}aPffg8;;2Fec5U@F1Dmz@o^O(g^pqPlTw@oZ2 zLo6i0(F9I1xD&}1R&c|T8yuqgw3)lbZ*awxtCTmalxNt*8vw)=>BLja$2<%LS4_lb zY{Z3o2f<;(hAG4$BFA=H1y^D>eLTjFEPiwRyS}T*nY>SFrl95U$>fl;pcyGSC;}kF zoP?5=rjW`=amq`$0=}523|KYT(uwF z(MlYY`5?Jh5YjRo3I1szlRMI7oX2i3sbcUWeDJFAnywU>(Kt;XHf_=#y@^ack)24= z9F38UdyQk#n^9uP=7-5!*wusl(~r!_n{3u)Jx%|0WrDvF$i$tq0165GNm z6N4?n7lx-&7D3Yr{u+b;!%z*(E1T)OCS0wB0<-y&*YrXz;v2!GVAp;vC`Q1(RthyC zBQqvsd>PZ%jx98JToRR{cC_HuI!F^BOssy`t+axyOtZ#;UDuBdy+YI267$#uT-$#Q zC4p_h(efO__$|6!3LKLaagBq)?bzN++$YRDt?k0m4a3td-Bl_~`~aBtxDhnK0!1PM z*$tT7O&-BKhGaaBE5HM_hyq<$c9aK|NdT`)DXN5Qt{Z6uEO5k2R10|{ob-*zBH#jM zhm=4Wd-jddYfu5`t={at-d6F2%i%1OaMb@aZQhG1#%c%!0s-Hf7|d2Z2mT6?ORcUV zP~Kgg;OX7oX6N20xZwpJnIc``Ch_7|fZvE*2M)jklKVJj=-+1m;+&Y#k7M949@h8z z)o6XzNsi=`^nijG6hPsb_ScqM*$@Fpf2$G`#6Yp*<;#5N&wIfX!R#&Uf{0!IBm{j6 z@)+eQjLl~3xi9eKFh08&@o#Mk-p5mP3iW)5|@6^4jt+YP3og=>I*nPA4AU6SdGL<&DHe7;Uh6upkmZQtM-V1-MQGU+f3j?(4=LBxFs; zBHoYbUDWTMl#s0C`@ZD-j!97FhdIoQt&pYp=NOaa*7 zT$<@(@PiT;5R>qSLh#7gFaBsW?mNH}AKE4ZEp2g{oPH}Le=E&RGz^d115ew*m+|_RXMf>Du=S!k2U@7^dobZX4fZRdgL?2C zHoyZFE%tZtPx<(H zUj>`5u>DT@{$Bcc@_?Ey==eB@6=CLu$TE&j&@EYykA7Ze{ta}_E%N|EuG`DXy!tos z%CHdVl)><$f#PCn6j`S&wmm9hY|awq59kW48>0nufLYpzs<7`{5fI! z12O$&iI&U1;b{K!7lIDwp9GVh3+jJ{>|gz&j_Uc3|NCF+KyauW079a%I7}XuNM{qu zlrE{yskIu-UbR?n7tH?^Z^_@Wxg1WP)o6Dc&X%v~-oxge$M5xh{@*VUa8R(2@X#<3 z5i!R`#BU`BWzV7U@ek!PkH*ojCdYw~GsaVrFUH374y7?iWKl5|c2>5qrW4i@QgtJ8 zvX7Gn7+3EzG>}vCuabB#vrnXEkadNY5S0{hwDcEGB()Z=krO5GHd{{wo$tG*HaWad z_s^GdUhi47`Oa8>ouAhMRJ>TFdKC}PIX~e1aRbyYQ6m?nj^Ww%=To;l5UnBf*b(GM zks(QrG+7emNtG#Cu5{TF=1Z9|Y0k7+6X#8xIeG5%*%Rnbp+SibHChzu%_2*WFm>7# z>QkvvsZO<873=?1ty#G)jU=t9gInd6egeC+;G?oRXy7SwAd-y(O~1M&f@18E0}eRd z@@wOk+OC7Q7AAant%$+~pQ0jr0EG=aJCT~UKZkzSpNn7y2~>8BM= z#db0*74Pb-b(`X~@fd)J95r4O`3%?rY+XyE-KHA&$hXN+KZm}vf^=^Hw3oKVZMydG z(roR*Z4{mQ-Mnx40yh6wu=Kdy&4S;3dOTF*MU)@ekm~GQ&UD|`j?Z+y1L~6wu>uJ^ z(7*%{Tu{LV8GO*e2qBzM!U`$8(83Hc+)%>~IsDMW5J4PK#1ct7(Zm!{Tv5dqS$r|V zqhw5KmQVjATY&}}1BzxG9Bo8r2p(_D@t!@*qQS>jG{PeW8*5lWgnDL_@s?I}#1D@l zEZe0UW^%w0$5i4H#v2=vByt~i@FP+qAKD&o&`N6G#CkwNV*>5=C=Sfe4MVQ)?#0FVjIAjdYL|5Ir*$ zjanHJ(^59oamnXaRkT(br_`0#U488p*k6SWme^s9Ef(2hl}(n}Wu0vn+GnL@sg5|R zo#PE`tZ7&nzpq7af?a^!&L+IYF0ciReTsPbF$ma71`C>&!# z1Wx}N@vEHXmOxjRFd`c`KS7-&)r!jUrNM!Er-($hFxXwwxFn*f!QueV{SDu8y4WDc zbe~jM<%YEd=v&ajb$DQi&wV&yDK-Z8u`uAJ7iMzJ)fs1*d$up-fmdkaz8Y*3I^v?; ztpa6ySAJPui?hZW=&il(8f>k@=34Bs$v*pQv(rXAw4IyYt3- z?!Eo)8*se?=Uec?2|xUA!xPsl+QwxP0rFTN7onm;Ca-)D2PQ|TpJ_c`W^-6Bw?=c! zp@H0V&`}3g^T=^xD3Q@6cU_UvV+uX?)pMtOk=9F9r1O+yKg9Q1aOY#o;RPw4mgE0F zf8O!vp`Tv*>Z!lp`s}gaUipj!XHxtC>8g?k3j{@Uxb>*l>zF-e*TN#1SMEO3SQ8H8N}cQHP}H8 ze$ay<1mOrpSV9t>(1aoc-wLxQmTnZsP%M<8_@I>!+R$)@F5CkQSIC3+%@Bw<6k-sG zI7A~B5s61sViK9SL?ha5|M#aWFh|($;d?> z@{x9U;v|IyD*XI|l9_}eOMdeuO^VNwyTRo4Vo;4up7N8ZRAnkzxk^{I5|*!&Wh`mA zlN6#5mmsR20&m&NT>jFR!Mxxuf$6|v3KN;fRAw@nxlCs^6PnMIW;CfeO>0&Yo64N! zHfz~UZhq67;RNS6#aT{rp3|J^MCUry*-m!;1d@)FXFTcI$a+Q+pYycmKJ$4`efE=| z{}iY{2^!FX0u-SGHRwVUdQgQnl%Wrms6#0l(TYM8ql|l}J2|>hk9HKK9~EgxNjg%K zmK3EYRcT6DYRhZBG@CJn=}gPVOPbzPra8swPIcN-p8nLQK?UkigQ=3K(TO&418ubESQ8r7jCvJ~Oew2a&q~&>wiT{# zl`C4^O4qpBb*^>Q>s|MnSHAw$uYtwuVEg(tt?EQzEZ}Od61!MgHukWNm26}wJ6X$C z7PFVtY-UH|%MJWOr=LaO9nKRR7opBDBFMxuviVunzV;QStzXlC^CHwz1%0n&?Feo9 zmImoIJrbJSRs>brq#_l$$5n1}nY&!)HW#|jm2PyYJKg1aPEb->Vs=AyfLG-Ns(^q8 zB8uAxA8yxu=1o^X!t331fwvIjEkt>3a9&}xHzxQMSGw#QlgR(XkP+|Y>}Cl(U;`Hz z!3S1wf*IUIul@|MYk)8lP>>_R=41v&*euRZ$10qUq z<f=Gi3a zHnGJErEoh9zv~udM9Pf}Qv#xt6Z!0JtaLAG1H;GyH?r8a|{5Ei^1aO0U5w9-Uw1a;a(dbn2Wj4FD|TGix7pQ6dFi{ zk8dI5ei^yPN^XObGa=m^-QlLJf%v*L z-PPXTDN&_bQI1EpCj3S|f$d-%!;+a2SeN=Dk2Gzn#BM4sm-!^?hgswg)l{)JOjHmV@06|IMV$i!Sutt&Bzn z!hG3V%JdSK{=}zWed=G|`q{_+A3B&3r??L{&-8v%z>k%mImH)GQ7u!NX4l_d(ZUKH0EeY_#ISl_J0N3IKj{q+&BmD@_0%1)CU5y53&<1M| z2X9aZbC3sj&=jc4WBRz^WYx1 zi}~De4*`J=xdbCX0TBKx9MaIeXrT(V&k?up5hD>2Cs7hBk)*1~HkR&cM1xMEV8Rk7 z6UPn;LeXl-jS^X|&J}Qu6)PY| zSfFfT5$I-d32HG5ip~jgkrsC`ZjApi31mzP5)BNbvFUD63?QNe??nq}>{>qY6yLxV z`OX~m4)6939nVo6+c6#AksaZ29p}*<4eFF~U)fAKIo z<}Up5EYtD=A=4^8LpDCp{rsbU1|uxH5-UEYW$dg6PxA*;6E#;;HCvN4U(+>X6Evy^UuIQ76dX)-yH5;-sdCWrDlzhpYWQwj~?JCQR?VyGmwt39RjJuQzZb6^#C zK(Vq7DHkCPMZh|(K{&ayKdaI}t@1z<6hRkMK^ycC>d6^bvEz0uLXAKihu}i5;Ijh6 z%tjG3WD)9?ATksa3Yh<~3I2tzdU3z-6BsA-WNbq)I+Vgb^cX|b2u9Qvt#|58^Z&=2X`Z z@gn_TPkR#f4waKSg-{Wd5VNxn8g(ewFgGV*_}H*O4*^h@p(F#fJRKBN8B|n5l~hO7 zRJBioYA!OvPncAdDgFg4^HQy-;?Pp?Nf8jyB(pFIz@{EZG-Xp= zXLDWKm0jP}UE>vA=T%-cNLj>85jo6r@O4if^&xt5CQXtDYDGCUu2dS25XRFFm|+?y zRYt%tO+_!=AoU5OWZLdc*9x`@5w_T1k6{hfVF{rERE1!XGCuc&WW_5}4?tl9>>?)e zQlC&Di>*?^>L3ADQA<^4PnBnP)@OSbXqoDsT+uGTP-K$BJV^Adh&Jk6)L^;bUQE$w z!{xukinfY2YK!(azyRy?LlzqbM!!l$2W3fH6lyn72#P~6s`omiuUTR#%{Twlg5A+v6tLRMassPcYU|^Dx-KM_90%^5XJ;oo|k!}w-5oNP~t>;b>wIN zH)sPGfCpHB3;31nl^@PhW=xZ|6w`y|f^0?Z{EFk^Xb*x&vu0wTGjqc)2WAp9ll@W- zJ-Gj(!_E$VBDiLV^@7~C6cLDmH_n26i!}d2gTJqXx2+eRB!tgWRwGmAPIxbS_%#B? z={{J8i9lPmQdf!i>2mlpUsdE9EkN#%Wb{}6{-S1L*giTbX%`5ClXipYRgCL(jLVpe z&)AI97>(C>HJzmbN{4q6L14zsSl$@%=olp67>{*_yadE{WPy*d^N!0&kApLg`*@H6 z`H%(qk6|K^9eI&w;gR__5A=&2XeT9tq>+~=kqy|B2^f?=S(HPWl&dGnn&uT#cXU;` zbb+jER~eRDS(Z^ZmT7sGS=p9z8JBmtmU;P>?~a8~W0Z$Em~&GMi5ZzoS(%fWnV0|B znXl5ChQNY*tJJ8Oni&Msc+i?#B%2e6ek>&L%tM9C=YiPRoYfee&sm+*nVr|!o!hyX zR;V&2R-7z3676{szv4-T#(VJjng6+&0~(+QTA)q!lub*Qds(4>nV}Q9p?%q*BO0P7 zdZ8t{qAB{JFIuBBdY=f|qYL_@LmH$nyuH`t=k%|-&(HY8r2fipQr!wu6a7I z%bKkFny>%buLB!c?g@3wv9Q%>wVG6`+2FAE2Dtdi0Z;%(Xdn~dN^?YvvTgLoT4xO& zd$FyWsy{ocLz}cg+q6gfv_)IBOB;fXr4f(lwMT+>Lsq3?8zp9YSG;7s0Cre(dwh>5 z5Cs9Z0h_o5+qjDxxsQ8%Kxq7Os<|srP;+|A2(Buxo=b{T{mBX*V|M>sb3_jedH{1(6 z{KT06aJPWO+vZ8*pvBw3aJ`1cVw`Vgd~9soDPEinRvfi|e6>@XwSyeUhup}89LbA3 z$%AaZeeT#m1CO6)6rLRQ@a^1$*HH-pNkR$AOKQrkq{`Xw%IR$lx17qO92ROZ&Br`d zgn?bwG#1o6zHs*1=)ujMgkYg0+w`0rs9fN>wcqaLnDaEiE8Ng09MKP5(F?fyviLl* z8=4;GEFql;%7cfWbqPL@(%BD&AKeBe-P6N@GDF?NlbF*T-7hx1Kq_Fo2`E=DeS^c0 zg;PNo90Y{3qFMu&{B~p3*}^@Z_!KF|td)mE5YFjNVDxg@D_$#M=SSe6Q2<$lWjMM0pFr1!Q*v z+$V-4=1vkXCFI=mQe{VwxAfYiPsAl41w?+a(BAVPxC1QFAAZpz9^xn7q%Rz8rxwTm z%5tO5E}Dkrcqsr z4Q3vOYhJ}gT;Y3$hnC=RrNHDhjzm%ZMqTE{k`)eooXDsC$d%mcseZ|;9_z1O>$^Vd z<>e2!7v|9oqq;e(IjGhgC2U-LWvXHVp5U^p<> zQi)mBLqb1`(-QU1WAvBRT3HYpq1g02?z|1q9neP^T}?1o74{vt^s6G*`}SM8&i4(2 z!!?r+p2Ag`HP3LKLXQ7QlHV?s9af3J`MCmEZ!BDGA2C{8&}u*0!++YxU;N9T{LkO~ zlgTGU;C*S}eQSUP{^3qPwI|r0AKyRzp`rcpU;cr>COT}0L{x57+^l#j5h{j)##YIt zv2w`Su-*f&Yg{6g$o(3DMOcAX?;P<)snVP!V0sSmUE;5sKoOmRct!Yc1`qZ7%MvZZGezZ!qw% za53?*aWe9d_DcV z{kwim4T}DOD^&UB{TpZy-@Sqf2{LrY&mY5p^AgplWTPUNiUJc}bf9p<#f6j>9aLzt z;lh$APpWL0@Zmp+>nK{pc+tj2eH}lB{5NtX(0YCjf~>JHQBD#3Sf*^+ZfH{g7e)L- zN>FJHrcaZsE9!CHPp$Eg&~RB{XHchU#hU+J>$WXiwQI^TW+cxZ4xV_Zo{rkD_HKg9mqJ6xV)iw@ttn%{7 z2qjC@vQsBM=6jLwwI~_gAcDJ>?cGIbsx=)s{^a@h>+jG1zkdM&IG}(95_q722_m?l zf(LM&eB;yf;jNFqWe z0)&%_98QHINS0{$6N*9RC?t_X8d?7&k~5Zw<2o`O8PARz_P8OCPiBZh4J!y))P{+m zfX{nIS$QOuTB=yd5H;HHrG;U_xXzmgRAfpW7qaIenKhJUwn3!aJ|L_0oH($NOW4|6 zFh#_qY6QR3PfQ4hwHH5Bq2&M7KXkB}FvhGoWw1O>K5SORJk{`(JQhC&l}TuoTqiq9 z*}xjgD}~IE$?mvJ*GNdzU=PJ_=?roW5-Y4R(XI_`bjC;*eKgZaH@&peQA0ho(p5ig zwbWQseKprvcfGaOVS_!k)@6Tfw%BOXMXRpR-iieZE1&}5v2HK1UE3Q>@kT|n1-m91 zB03;2HFfKK9NvB(p*R+Om-nR?f)hiY8XBQdF1X@pi(_n@k1-=Cn$yC?DQj5ng$Q$> zaVIT~6Am!!uge}g?X%lnJMOpZo;&Zm``$b7|H(?^LrZRC=S|1Ms$%hK9xvi5aAL`F zhE^=X&I&9R6}^Wm@Du;%@XH6;{Dn3G0DTeCJAR_|9#)Y3Jlm(={r8BLNu>AOZ@|ya zVDb~{{Zy1c76GIp7XtuR3T1_kuy1`Ha@hgr;lL_YF9+9qANa(l!SHc#gC6W42tO#o z5R!0&CM+QePpHBavT%hiY#|I^D8m@iP=st_9I=iNhI28d(g6fP|2_$~22uq|v6Q5WN%E5vNL!>0uOJ}oXw#9+}uvRVs_mLsiWCg0M z3HL%N%W;acoN7DgEX|otbdr;u>TKsa;|b4szO$b0yr(?vnNNJulb`zRXJ}}b#=-GU z1O_E2CLACFgpNWC23_bSx$#hj#zCPJ-GW08nxBalOQ8mp2IkzrQBf=uifOQDL@TM% zl(KZCE^R4HUn(sZUaC0q?VgpgSDGN(Dp;GbZWOd^D0MKe?(Q2De|giOh(iW;hr zp!zDQ3Q7N{V~UfUveZGV`n0P@^(t7uD%P-)b*yGBD_YO0*0i#9t!`~AT;J-!*dZZr zH_ZY@Yf7O&h(WLAsKDN=H6Onc_O69JtYH(2*u^Tgv5bAJV-va0xKv~j|NJMHF6++0?RjwXQ|&YgG%|*~+%It)=a4Z5!L%@^-hrbqVak zb5Xxm0kQ-tZe-8ly^A~{xz082bE6C0=}Nb{)V;2Cv#Zy*ZomO?EmBg!dynrPHM~C^ zuUykARV|p8yzWgeeBUeI_|kX2_N^~|@2lVZ@^`=f?XL;7D`4yrc)$iOFoF-P-~=;x z!4Cg!FoYlM3*7!zw-mmxg)`h+3~yM&749&IKP+MnkJ!T`KCy{Y9AXr&Sj8o7F^pd< zV>m^)#uBzMj&H2v9P@a`KJGD)e=KAk|F_5hE-O`!tYjlIdC5+0GL)Yz_OqcAjp#)en$d@D@tY&fpETkz&X$%mrZ27OOmlkEp6)cLZ-GLC z@&O8_E;Ws~jHrhqiPc)x+o=sYKOWV@yQrFit*c7MQsb#rq>eSJp-_Qe2OHH0ghKz2 zNUiE+Z@Jmbezvot4ee=5yV}&gwzZpGphPT@3tZ^7P0fspbcur8mTZJYyd6kRSOgue zyF|MC8V+v9``hV00KM(4?{bC0%%=-Az&|bUfD?S+1}`|mBO1>>R3QfZYq;FZ)ab2| zr6m=2bXyvA_3Jv`!70Km!}&JT#c`~ao%Nk|ku z@kjGq=RLR z*UcsGB_#3SW=}ZU&#v~gvwiJuHwO)|w5R);H_3^TJCg9OWeU!tW{WC92HF3M-VDvi zL$gj%pMv(M!?#yOdPh+^9*-drnaM(RJv_EEpKZ-^e)FF1Jm^0!`p}bp^!$ww)%^uV z5iBqxhCBn7yxkjL=|X3+=LZj=B}e|{-V~KSxk=jWa^d}s(Sjx(yvuP8C(X1x5 zCJarE>Digcza|M8;>XEDpX7iDpjc)x1eer<{f*p0q>=o=-!$1D7A=efrkqZUj7d1a z{N3MXyq{KhAW%pX9a&)0A)N^>UDA~x3Z9?}wqOdrAPd6a3eKPl#vuRDMToi4j<>xY z)~(LE5dq$8A9lO~_7UM5s0VZO9=%xI8W6(jD3pjK-x**XLxqPF(g6(+6zkoG5u}kJ zIRG7G%ijcF8YQ0w8X+;{ph%%18Ze=?{NDP-A^XW89nPU0)*&7$R@_;Td%2Q^I8atq z-dN0u{b<#Y1dve?V)+10TJfEQFwlnJ#)%A3s)0o%CSZ(^PdxC$+{MEgVjh$bAgw8$ z|A;}PTpW>*1y*gMR=Er)mL4pcUM$X{EY>0|-l8q$A};Qtl!*gESsxa3-#GkYMcvJC zFb;ZbLGc-r6dEHCx`7(LK{P^wZp4D*@QW%OV;_Bsm~GwFK}7!%XaUu|8y?IN7O;zZ zI6@kfT`&ma6tbZfSppW)ULMxt9p0lo<|97tqdwNl&`gj1Wr<0=--S#KK+0bLvco~n zUqYTm&{PG>9N>jWqC#$<9u>sJu|!4!MU?;+HSL*1(gT_d1V!q@5J6y6fSf?$LoM0J z1(M7?d<;CetSV7PNnz3<62s!bjVxj^otuh z%h-(p3z%c(^hPtX0jZ%t$g$xR{hnA_M{fk1A0R{69U=c)qK>!0AwNE5KSHKtMkZxW zre!`u-04XllHwv>%$+Pr1Qm+p1<7b~i1(OD;uWA!oT6qPVsTcLY=)yQE+;QGCv!fhb4Dk0PN#HMCv`pw>MiEJ zRLgf%z+8@H8fquph-c;y!FxDnu?*i^wm~u~=MtpOa*<9Jh?d;+!8~F?U>3(3yblks zK*IsY9&jId7J)k|XMtWOf>x%2CMbh0sDq}T`+X#gk)W5v&;HFMDE-Ao8pMV+1tBH^ zX@TU0@|2G>Aj>d?-Z)Tw=mVCpz&_Al$7HBGkRbp6MbK&Bq*LJNKuV}mCgoA~D3JcB zkTPYE66uiosF5Zqk}heHDg+J==)4T3S)OBEPH8(r>4Ac$h{Vo!0%2Kt%j;o6bKqrS z8cusQlu6|fG)&_lL1|(>D4aH^oW?1g&Z(Ue*$Sazdi^H8F(OfojE2A_px9(8{*RI5 zjG;DO3-ur*)<( zs;;W4wkoT>s;kB-teRDJx@EUC9IkNZ92wB%WF0RcA=rh~8w7%$j9U1<8uv4}O7qh9b}8q=YD)-mq?ct3HX&+Z|#* zisPmm$toYAHy^1z9Z4^Lo1Yw;uo@K`pG5E_6vL)-RxsYk4 zvSOvty4A##CNH#=9^>7ai5Yv66^tq)U1_2nj$~I%u$Awo2F#XC=+4qQM7jCI+8=Ab zebCAjM|ulcLy#G*m^w35gkYkOQe-b^?PQ0Yyx@wA@k7?ZhG}}>@eh9e<4S<}Na@MM z@yP)5=|tt}0`u914I_#g71T}}V}wgGE*PfwOWd4PMQ#=KLW(x7I9tzE%K>>VE&~vk znAY~9w&G6<)2)8httrc0Wnz{!#<({cRB+YULit0-=|eKhV`bH2Bg;dF?PCwi(@51* z9^6-GrdUF3TSpd%O%>#p1qxF2_Vc=5pZfPdL|#}^g`681O_4|Y6x%2x?&gG>E0yp& z%m@b62xhE^w$+F(7kUrxs(T1+O@9KkXp^>Ri&ba` ztmt6Ym)i@pr)qRqHVg>9$Nnq~MOSQu=?oi)URHJFn>h-cYIR%=KWYe+XQNx^AE z5H=!2c5;kba(woWviJlvwfHQx6g=#oglj)Zu~RD6Qt~W+Bxa{JsHHYzr?IW2Ie14F zVow^uPMTs*mcdS!T}xlY&Ja_}kblKc1g4p^r&(lY+N@Ea>vdJsbxjv zV8f_m!{=Zp-e5jq=U}Ph;NjpDt|J1mGiqMb53+L^)Nz?{KzNMncxiAPE**mY<1~~*L>jW1$K5y22KB$Xp;1Iau5QfwVBXWv>40sc+dGR^L z0_w!bZp4V|ztC`gk-GW9bAyIbFM)9*Va6$GTQB*EK**&!c9Huq}R!1 zqS$Dn$*rsFY+}%8YQ}A*%KeR#8z|rmjA;a>-2TDlwjge_DB`xPYP1A#TNQDdPd1t_ za$9dUTJPkVxp13;8*T5n?QCyt-?`XbbN?U%F&=Q+uz>7&cpQXzGW#1HJGq?{K~9=H z&ITZ7mLi&nj6gS#LkN#!49GPF#Dfa@QN-h3~N@FHGFWQeQh=zYZOecp$whF7f=cURT|tanSP zG1pD8F1&Fp8k(nQk%!|97MSJzlN-KGG_|Az@KW&Ev1Q28h~ z@d)p6#GZBJb6N#bD{)h4npk<4(NZMA`;lX}sSD`J*5{uW-5pZ>iz82f8zem}52ZR|4IO6M72}VJkRhs$gM@7%b!PpJGQ9G$Vo8 z`jMZ<^AFvNDdeDW;o^Tf0o8Lz5WFQJxTe=VD*9}zz0<`;ydK#^@ZZ&v>#MM% zfX+kxDsN@7L|GtDTVN<`Ek8)0|7U5`js$4%J`&M0>YZR$N^**Zc@cQ9#6Um8^n?9_ zENH`h@efR)u4d0CcMN$!_AT3~E6iS;UZm_yq2T9Uh-@G1WaVT=1!%pWthJ_7kQQQ| zVnwg|@gjNYId@JfVOP(ZsI&+vs}L&d7=|sLfbXc zM>|;#wP(sTp(XGB`7=`bL+=x_KtjHrTV&<04Iwy}%AUMr^wOI+N@Op1KC_K&NI61& znPMkL$X|jwE*-vF33k7Ap1Eeaj#!MD9m@_Uvr?t`PQhEOyq#BIjO!}VQ)oXX&4>^5 zA!t|RI@8HznV3m(qzN@Tj#>rutp>KK+&1A|(ktO54f9G1c4Pa3gLPtgY-5g>qAXvM9$>E-DyHJn6s04l}`L0(QS+T2E-$(FkQ*^fh&pCmg! z5{2+9wdG9*)N|mb(-jOWjpnM)4=pGynd~xS5pnBoFvuuZb#?djiVts{p?wkmE;*<< z$*D7U9I&QI``gUy(e26o+2h6Y)eGY73xPwRE&FhW#h!D~l1V=A9{zMrFq%}xVF%OH8xw=5~H zz$YOYUQL-dt}4r`Ij0qO?Td-^We+u8JEoW$Lp+U&LAQdvb>DA9+re5`$&JVcM3wV?qjQ$l#Dg& zFytrRbI8Z3MR0<`Vaw!tu@muBc^-=@xd?yYENbz5Abu^+YAsRQUmIque0L z72(AsL|yA<3FBx_^$MId{y8ca727j)e|5>_Cfq5a^Ex>{p z4Sw^&6z3N6;_PI8i_%}ONCH_x7Ly3Lwb4?RS>htlr>UDWkJXf28UmJ$pxYDn#`V#N zU-fkZ$CTB3hmWV$UCtJ^Sw^K#pCq9W$=?wzNct%>;48LSH-2Teupaqn&kjiqPseRvZUOO)-V4p}GBDlX;_qxCj>gCpd7}?X8zG#%7d4ZZm#LvR)3_&Ej znuB1xIZV>Li%{-veu7s$y7FDJp6}Xi-zV>USoJJQxSXo+%;q-G!bWLvtAM)=fZaghhy@sZ`BCkP78pGpBb%CCP;?3!%n1uCON2 z+j!DS<-#wm4*ID|vct>MLX^yESCg=L%8VqCpmY&ZIhu2RYAS&{aPBgXqK}KZ_=OdP zEz%g*m~hxtlA{nS%2Lgkc==wEX8Ep()ne|OEVb@et4l78Ozx-WS@rT?gS3k=*_bD^+ z>xT*0`r7<&Kw1gbNBR(cl#FZgB9WRRg}|PkoDR7jX2oMR&tU1)J~e;5Xz8g^cqfZ5 zcoxcG?rQnecqOvx7OH8FYDJRs7I-ER=u#A^L{kHFm@~**O(uA>oK-dwX~Pi9HvV>^ z;b7W`(PI=Z`%j{RHvFU0@wxQJX>oAGJ=>cxze%VkbC z{5iPR2sXkxYaX~yin$?=lplA`B4#?%JE;Bv{UE>-5Je}PrjZ2QUse{A`FT~ z4c@NiY>`0~HuJ>G%L8|1+h6VciOUTUq?Wp|gD(P^^uD1AYxUy3N$><9ncvVumU<}= zE<@$l@*~X?5?TPWH9}O}HO)})mZ3N^yYrQv8`pBrZnt`}>t35AvKGT>_aLmRuTE93n7tdCdReveSNz zjklWsp1qJQx{eUpWSOA7OsO`UB}h69usoarqmk|OX!fI&VnVx+gO=Dxnbu)Bk4Gbz z-3b+FhD78Qn0%PDS6=4#OARlU`zGbHJ21#l+bXA0Tj*M^ifXeiZwR+mT&?1}vT**~ z#1hxwE!U_h4aG>;Mt!w_m!$@a+{}k-dx~yGb>J8EmFdFU`iigC5Gw=S)#d%$#`Y*U z*ea%anoBhArjRZiuc2sCD-!iCv-UQ(_fQ>J+iHPV;$S|}&@em-pOaL+z3wwFiw`5x zV(&!mNfkH)yjZYSYcFQldPH{#ayvV>wf%fztCB`tKMoW0MSMcy)l+s^WmMpntE||G zHRd6DXy@}k>E603y!(K4ZWq)^wLfxp46(Qnx{M=fw?AcCs#IBb^}&K-IpBr+zHwcr zz3s(wr>839FBCQ!w5{n=)!MD)2(AGi<-SeKFjwB}G>ebo+^tEM3-wF}e_-O^A!(aL zzx(B}9%V7fKfKC3ki_%#R8Y9d3g^|ibQR2`rRZ{9>1|(p2NB;S5(W)2DFVg}^smzn zp7%d*x=s1$-{jJ~9LgKG&t>cXDKXN9kT28hFJbOqoI>F2gcb<2loocMHf z=nnJ@M)Q!vtPR3E$SNQtX}73^67Zo0^96o(|0?aqNs+<^kBre+6*#9ZqN=v+h11CR zY)wTrKcV$n&y!wGv`Tu{RwS$>Q3{vUl8PV&`-)#Vxtm2U6fz~03e&0i*eQ!6q8Qt` zHrl@7AhKCkI1t%wZc+JI1D;hu`jO8inog=h=EsQ%R1ZEH+fFzcDJnf?3g!$mueXZMhQ^uxx z!cF(gO`j>oP%g&UCdM=-#=IuRvW8fC%m~*SJyMJQp%l6Lfi-0dy|#-9W)@c9TlA_0 z))gIch7tpwUsbbFRZA&zZC5WrGt18{Ob7)=>oH3Yd*AUD6=!B;|8~?l9qAYn3ql$r z_7m1MdqwUEA7>_3ODVD_V+oFKuVQoAjDN2@Y7CzV_Eim23a_|mWG{kt|2XfLA;0)D zBIK5E@i@cYeSS>L-2Mh&{}I2qU^=6@udI@Wf1D zEUqU*u2j%EnYrs+LIFn~ZL}XR)4BvEIKd zz5Q}>1A20U<&A@1TB!Iz3T=ZPH#s8^O3K1IBKO2{Qjec43!+!$*bfgCm&l^ z1$o;y{E8`C(CV&1`vBcS0ag-f}u z+SQf^QQ%IBw~DNcDG0WlLTBVzQhqL#U)O6}$rXHg;M-Y49&7tl9U-?BDQGz-Fd>Mn zmp&e~GqH0yu_}k2AR@VG#dD>WdlRU*j5}$QIJPDDX=84(@#!6mJbG8R!ozM>Vd}&O z8TmY83OM!rML|+TJSFkd7I2?D=?Gs>jgsY>BGlK(sS%~KM4nx%>3I)j{3vCDpUQ+4 z%0%tT#N)~&>&hR_l=sq7KhMI(K;)7P2u9qeM+lA6Ypfaow6fyUT>_$k|Q&0~$Yx$hM!-6_^OKeJcIA_Wo1S zN5PXB%6bm1(xGf^71%x*cC@bWp;3QaLuBwa&f zYw&0IGeZZDEcG&{$-cm~u1;Rj-oZZ#d@OLCX&=a37%bPYXxFeD*RWdGu>0+go0HocBjoK{rRg7#y=_;+^k7TQl{24kl(KYaCYGATD zu%=~kpiAmz&k?BXQrur^+u$P3e_)?kjl3`}F!{=pK3TCHy2_ed` z-?eCIR7-XZ+M791nR}U`@B}gNNzyU{V9d|*UNHpxQij{CY&6I|fN_nk} z?+m%}I$lv9ynjlr5Uf_0EqxT#-sIORt61iXTv>CA2$^3l`4!yqp)3NeB9d!)iReps zl5VJ{UQpy(aNyc-l3pyNU2mjcZ**L5VqI^tLT_q*ZR$g=Z+ZZA6D&hQ^6b3-XI8k` zc}&K2q^N=-8?AMT;TbPp9NL1S?dtVJ_8|GD^#T*<9j$qjr5Rzbb%FcTy@qwcX-<0L zMmh)S?@x(aA7ECVWN1!>_U2(S%V%?1R3lALR_fN5KHzRQ6q?)VQ+?0~KiyE+8432) zKW18&7g>*>6IyKFq-R3Xyf;u)?vCU)c=~Qo?1}JbJ#uUfbKR@&550kqg$LyfFJ+;R zrj6G~f|Sy{vByOt&T6pXwXQluGd~~e;BOc{y_NQRE9nUF-TBt;o`J87;hA-FAqiu0 zb!?&qeM$bfkIYHXJ|ClP7N%m*DE$ zPFV!PLMn{MiDalOf7A8$!2RTHS{pHZa}4=1<9_%^1ToKkWql0Hg zd8f_L*q#HUE48nzc@Js*rxvr)7nt1z*8tJRJr2?@0^FbzXiQnCo&C0Sz6HjmXJzwb zWD93SHi~^T$nRYNZgQd5DfIF2r<*+ybW<@&$CD7bSB#T>0ci#1wSctRA36-BDNvcQ&)RKazzd)p7Fc(KgOe5UWMe z@R8frk!kZ0rD_VB>T$50MfjF^dhl^!w?&ArxjWQCs*O>t^LDL}Wu1a$y}o6`ZRqsN z+Nk>SL`Aj8C1+e=6lbR8bOTE3`;3@*%P#KfcJxya^J#amWsi_muYwi!sAZq`X@9U) zf0oq%B>8kC+G?!OYINgttix*j+-ky`wUIg9tEIDd?j1MqBL`?RN*MJn)>8 z*8hhy@b{~!QM<%>w9UnW%_X|+6{+ovtj%?D{!wM-DYNaJg6+Mx?L)NfW0vhxrR{T} z@>X=HbWV89tu@ppm_IgX9nB6F-wsaL4qnj?!N3mD)(*+X4*6hV>elLAl^rUgH5$YU zz4HR??gD-C0uy3~h4=%9byjr{6Vt$;{*5y zDFpI^48xwB%>E;bJw?&Qv%=MrzCD%V)vb*^b&Nf2wmn^yJ$yc>vPRe{)4~hZ9&Kn!H^%HtF8qPu0MBP3xgd* z?i|F(9KYZ@ieorRu-u57-AD-E2&Y{0W;;q(Im&c8%1%1UZ92+>9TgxqeW(tsWKPN~ zPAbAqs)|l(22Sd>P8vQ=nlVmV*-qM3f7I_B^$?xDPCDstIvIeS3?WY6Fr1BCtfhpV z0aCY;iq576&SpM0W}42xY-jT~%v5x13AzPNI@@eI+Ya8^n#CHC-F;(mu@`o6fGD~+ z8n`&wx;Rfd>BhL|WV^Ukxwv<_cucx@Zn}7ZUA!SKJ{YdPomVa{E&(jAfx@mqimt&1 zu5WGi&=hWKM3?Yv*9h=!_~3m6hD#LKH5&5P8GeYuaEm8%OSrp_mvXa=aZUDdO|f-L z^>Is!aZAs3%K$n0O+NT7x@B#;WrN*vAa1!B?s;VH`7G`Q!jF3;ZiSt0MYit6K5z5w z?xor8Ws{8w_-;v??iJw2#Jk6GhX*p#bdh>&~MK;?a%a*+b^p%i`H5?AfpAslDhixcM{~<2eM$_8hMA9O-s^HUQ=W*Q^H=eiZ3$;UNg2{rXbIyLC@tFua#`C)he&GPOk{V z=eON38xXHe4DT&6@82xm+rr*Eir!w6Ui*tL`##*I45<8z(*pLVO^1 ze?7Jpk3~NJ`q<(4{E_mx)bzQ*|HljYo9hAb4ODe5$f1JHeY67uj9DaEL^V zp>K&Y5EY-_3h6SpFBF$Xr#$pBZy=I{$Nd!PDt{=JQYxJ>?5bcSkx{e73iuVYU7xe}E!m39j*hJ^~9R{NJ@F2Ck7%PT=1E;bu2u208*M(?V&x&rTr5IkM&wtGT}8Jx%N zYj%ec@pXee-R$>AQUxq;#~$hqrwVv75WL(SEhbEL^S#daxlfmCEoVEd1$fTaTRg5` z&jffce)mM+2$R|rU*(LY2-u>(G~XP~73o$*zqH(*u7Nzj=&!B!7rTQQEHSTbk2j}- z#DkP@&Chq&C$pWl!osgFk5AVSuz@cWI<27}9BEUx1Jq8G_&bcnZLe zK}4oQAA?Etk+y@$!U>E*sG@-sVf2MV6yeNSNIT&yBLv0~T(?dJo=E3F!x+KWL*rNx zbUG7AoH(huNj!kL#3W(59Pzt{ilp>jqJ~D;UXqUK@LsaMGxC0lQLyxWs%diAewxLG zBN+}qKQc{%{ebjArt?DB!B6-7;e#yiTVz@~=5oWRsK|8(<2+7g3t&D6e<^K2q>zkx zL6U-nd10yzy?IfJdnsLUrnihm@vmqLi_#=MkpMJK$7Ew{+gEAJoW_OllZux8k(0`f zTa?qPp2vL=bnabWv0Tir_Fd@{g~(@hGm^4r^$QviXALW+qi2m9&hO4a+rh0WsN8dg zy;L-N6alClfp)$K>dkbP)wk#o;P%J;QEVnE%O5yOfIE^Yq&VGctEb?lE8H9R0%+7)oLrjxX?cXyl zv)?u07v_83o3b|}f^^2+m4qp?NNhI?D}3fSr=-Z_G_Pi0?X=Jg@P&c@n$Id)HygUu z#mZJL(u=~D$mEYiJ2iEILcw_*i-|ocf8OMNFn+h;eTR0x>5r&zzZHxheg8XLDgqA5 z=Aaw~C63219@e^y7z^dl#@pr~Gyln=j(FPs0)b{Mr>FZDet+;nr7_ymN$sG*(`n;k z^wZfvxlJ_{vTBb1Vwa*J6lcmEg@4V!m7Ue~r~vrQVhbSYMdng;zZ_$+B;$aL{-yO@ zjo5s--%p7Vx<4q(7JB$g>wCgcBMPg(M&h=sj{{EKqcDsP)yN&yf;}aH&d`KKeEx-h z77vYDT3e6A&g6nS_Z6N6*o|sr7eI7Mju_$yQx)WI*>jSP(qr0I3;hLdN>%^e&myt! zb7yep@dW#nMtr!jOg{wmGBG@m`p8h&Ld8k)up&bHKGCy z-|i%qgtN)|Vu+6n2#DM5fx@=S*5mpx5!fS;yOjE)?ORTS8?&cAl{G7m8H7~~7Uvx_k||~YrkpJT z6O!mVkrEiETquNvPko{*Je_~j`d%b_XELV~)>~mu0sT$u+dOJ`Y@gbJOI<{9*Eb|_ zX{xwpaEVao2bu4WKrAnWmat3J%T$I%2<=AI_eJJ<%X%1d;;m{I{tn>@xY%!vO%&-N z*B*zgx6#Y~1_ z4sYLvZc84c7s4T%8atxUFqF?ynMI%l$}zP_kI1naB7iNz1+JIWD@(Z>%D+}v>b_fD ze4BoT`cNj)DmwMG`XZ;Tp}W^)C>Bi)74Bln7lrmcRC1+b^N2OGL-xtqp_og{Oa-%3 z)o9i(b}sZoS>B`#wmcJQK@sAvo}1UZcpWQ_Ohndn)WcL<3OzCZ+NcVzlZefyW1i0E zb#OM3w1#75!+Tm@pl*iLFad%wCMsmyzfWtKLY&LFC$i$gh$V6fRh6KZc%eGz^Vn|; z7uP-t<{zwK7h2Ww8sCXob0WB8yNDR4-cuCs(5@h`6gJg$3f|hr_?+&L!@3QL-G=-B zFs|ItS4lvPTf|EvXE~KwZc)qn7p-q?N~;_;w0BL!;qj@|ia0z)S@V$P-?YA``@Fpm z!U>+a?+p4)zUoi*k)LyfeL)2ZUq4LxqrYi=KOc${>r4mPaONrZl{(CegiJ?vD@B_`0W>qkA$ENlfGc>1=&epxjD3oN}r(C<6n z30?T&2kz$`Oyx)z9jeUL>CoUw`I9o+ws0@0e=SjfSR}M0cmit}f zTT1jJ9P%SR^!uPi8M)=dC*qfEXAXsnxRT<%2l75X@MpaDXP^yWLV^Tv@dmH~0(fXY zH5>X-1N>WAeaL77$$0}M0D)4#K%*MWSCA@RDD$>DCOncK1|Uck7^EH=q*)xKJrwj^ zh42(0Bx18EVrwa)4G_Jp75zIj zdIuPNk{TUw5wThvb%_*nO&jy4SZatiYPvY)VJPP5Fy;j*7DB*$3y8e|#=?iiB0d-& z6u%jKqtTG#Q0d|@_~Ovb z5aQYB;yLINxTF*AVI=Ui;|NL;h=vn{n-fHj62z7hB#;vu9iezR4Bw=`^23RWM~TYI zpUUI8DUafr!jd#glC+1DbdQq0x?!0dKqYIya#ph{g(a($B%2Q>TP_nNOZtCb*8U=$ zA}O6HWuD>`mg3=-;#HF3otEMQ&3S0gXuq5sa%7ksT9O(zoVrO4ld7H?ZiceUE zpIb(Yc}9C$Mk{h=N0?pu1$T-cuuuq)E?+wUBT>vy zf&H)*fA&*ht1r$W^#_`c115-jEB?dKm5 z7!({58WtWA85JE98yBCDn3SB7nwFlC`7l>R}zqfaG_x2ACkB(1H z&(6UYmsi&}e{S#YA0D5cUtS?luqY&e>YVNXBpiCZq3Yb;5X?_9nSh$S{s;mNi?yMe z{J|LVFX1H8wFScow5p|g!?nLgQ&^2hGNtPZ$1`{xj@O3kiY6i1A^|8LWa^8j^8pF; zUq|XoW{Z^aWq!&ul+KrF)mf~MG?Xn?8TN*MkZmkqt^>}NejRPBSOwYbjQo@ZRj#+X zULUWIf~qzw56*%^%eB%3AIT)Q`#%3--N)?9Zmne!!rRKBJD zXtr3jOnC&UycGoA> z`NF*iZuucm7jOBaavyF5U`o>d4#d>}{thBAE&d%$;(Q4C9YP*VyB$iI4BQT*EiB#+ zXKXy&j$j?2-HGH}0PaNb?icSw3*H{?#E2l!?Z%3~H{Xo|P?zk+%W)s=CMZeL?Io&d znC~TNnU?G&>p36or5Fa&?WdX~oA0Lq3rqIXts0N^GwcTF4l*4V%nyFL?w1^7dEOo! zWcwn}ALazUw>ZoVr7k_pi{w5&%#W3%KPpJnusHgaYFc_!nCX0cRFo4;e_ULUY;jyt zTv&QsTHbhkTvk0me^Oq*U~y8>v|oBs*>-z;Qq_gPa9Z8>-tx3&h`Q{wc8vSvv~Eh0 z;jDg6!}6?Q$+YaOan1SU473%@aNe|=Y%SXc%*Z#iu|Id8ofU;ww>ELeiu@Au2V z9nZHXZ<{j^885ov@U1Sok!Z>zFM2Vh7%%&9HLWiD3Czkb2S{8_F9*p(7_WvX zQ>?CrX^YCQMi@b-SEHFKb}+%GC!TxFWNw!&YBJ?pU&Iv&Y!?th%C<+efYM|mqRpF z&sSqS;OFZpDVCR;IZfM_KTBp+FD2Qw;Fr6t5SG{b-4xr`hr^<(4r1DJ@axmXAPeOA zX3-Y%a(_?-d40YEzkPzJIZ%jzE*PQDkX9}iMRTaJVNM?rKM3XsdgP8sT>=D!S6ip;|;0`$}CT!w3n<-G?D^)tI)Mi`>z6AS_d*wZf~ zfpYmoi$eq4EtgStk@+MCfIBPuQnlXCBHG@h=WfIJaE?yof9kJdmDrU%Ae@A1e~N8=mxj zyv~L~D;7bNner#P$w87Y7Q-Kz3g*Ab#f&Nzr;(Ws*SX0f7%!IO8JUiDzsV|Yxf>x>&A~Tmq^rsjg zU#gNaGFQm|r$i~LRINy6zEtN=sn&R@254ly()~}FAzGQ%pv*#T`k!*3e3{PT$U&nm9FDu`gbFme2!IBzWw_Ku(C@%M7PzU^5sVOqe}z)w>7a*|xEWSv(r@dFSM^_eF8kq{vM63*CS65bU8&xxy zY+OcHHy&?6U1*hdA+l@RM0ZU?@|E@}qig&8cg<5#m5xQS>&H5GElcB-&Y;otbN9Q} zEwn1vLD`L~^t-l0c}SJ};^@Y0%U%0LRF&s}?B?UjUB~@+mG|A~=Ii5KClq?MFQVKQ z4Do#zl0vmV{@50RzR_I+-{>Cqz2xXMp;B_&*ctbIvqiZ6~=_SZK!v*W#j$f=JwM8n&*vlIQN$Hgx6 z#_ACHQzPQ1r6GmJ+LZBAQ-P=Dsp!V~BKb25-KUkMiN;3I_?eBz)9MyFsA*9C+&<%J z?N9;KvN--`b$eRBhz7MC$b;QipEm9%Kpl7EVDG1=O(=|}E<}Y3f8ysYB*ms4{E3TT zf#=_;gh!t!(J#YwpSKAnn+A9$E~7o3cgQiChols);xnFiX%(ACG$*c-Tc7t>W17dz z6t2@(pZ9qunLMW|xiHvZ)OfWYfzY)soOjpDvaxB`=ONL z<4nfuO|fG8vF7CCLhI|F>X`Nu78l5#=k}G|9uYl-;w*@k^BFDkz3Q| zVK3F}Z;{*O{Nex2$bGx!@?S^p*T+-Pza#hm0g)T61R(X_L~gSGPUMc7fc78BBL9~* z4LbA=v@!xT6ch}*VxQtk59ZD!>+xqRiEzB}1PtW=Ko3w*tWZUW&;N=ZDA~+* z=>8w0hrjMYj@)tq|2_1OD#w@oe}f(*xw*I-|EqgYWA4a*LJ!kHEi3p#(h9lHm8^HBoQ#K+7p zb!c770Hw*#0VtW;s`V=rzgYf>9z^j_E|z{A^hV*l!$Q}n+TK!wU>{f$$Xu=c13fs= zKcZUT6EB>0)~)#X9B5vUTY$ORQ+&`g$UW*$d<_199uida1ZQ|1ApOG!_|j_78U()5 zH_3($4_tpo4#` zl46?ocl5AeT;L(46}C})^%*P6Tt$+NHvX6MKhOg!u69d)McW?jR?5d*ZF5@-G50OZ zwh4>B(1R7FW8=uTgb%@oj93EFx)s^v>}h8WUf!-u4T{YFKo2Al{KJqXqb}{5%={DY zVT1gxkIv_RM-MWd{7kM#@Z}4og%v!pT_v?GKh2<6#=w6=4>e_{YCk^J5J2$a2m0la zlZ^q-0;V-Mf1w8mEsnXW=Tm=}u;0Sf7~Nm!fk%rCpK=n(mh|%hcvCVH zHpn6FGyd-w!k70uWXi9nr#{%=wmy_Hq*fUvck&C&4u!m`$m5uhm+v{*`1?Vf+)Eg~ z-*Ys5t9`q~Ip3rz5$hk3io5RBr%*89enRZvgArw_y~x+#dV1>8Pd>tYQ{d0W%Gu2y zkK0vak<%iTqeZ~=_6y|HgYC8_;3KS^`buvChq1}pLq-Y{qT!!~TEp5$XI&Xqin9Gp zkKoN)>n}w(rbVCzD%7P5iO>|mBoGDFFs%SljT!SDCJ4IT>06*1?nXRMY)PWBLueyR zVm}REq%uUeoqsha`bs1UQ_G?g8Kt{rOm=hE%@{iEZ>wZNNM+tH6Mo^jo(8jf0mX5Q zH-(z9=093mFsvf;G0}wWJBd;Wr8-JXm0^ZRrYV4cnlc7ODr8 zBqAmgl~hhyB6jG8QEeiQUFwq1^Y#$BRRjRjs+mZoJo=lJI572}xVZ*|qUc(rU+u2SBj@L*9qEFHk9 zX<{vZ_1b4PcRjSU-XVK+Xg75kJqb%~<)AOuU!B4w=EO9XExw=y%~#s?Ga&mBE^2K2 zNu%&c$6iIz>_KO#LU~8<^;>Zt3kips*Zc`mMVQu_gobW#MNfP@l4j$3=i20PE1g#N zLDlb_xPH2*0Jm9X}V9nxI8TByxRI@J<>*W8S)S)(Rj zNd!zMahe&*hLb5ivO}rmx3?2T|33HPjZpYYa-Ie|@~6W)&W(75yXJUQ2-s~ng5-!> zGwTGN<tatb*r`(l*OxXp>=E^(NIAkL9xJ99`4o+RTm_{+6YiMvjY}ao#IMuuap7MibtMsI8Q$(Q z7$%I_y=G<1lK`1qZPTL^95WCr4j4(?rvnCVlIlET719a05;U%oA;_Qil{M2x{Wp>e zJ5iJA!F)P(V({%slTGvZH5cG34cI!gXA2 zn7QMM&fxd2v@|y={G)bg<}B}cyBAuwBnMvD2+W@PF6Y6`Xe+-DvvdPQjY0X383tB6 zD8Dv?O(I%PtfiuF`k}%vT4iary+=J*wj;=U?tbo|qwq??fO*;k!fh}-R(+0a4*OZw z&Km5V`@-7j`eaY1zcx4dZ%eu6J1nM!IiIW=I^FBGM;>!s2~bV%oL<`d{y%2 zNu$Kf1ft)G)or(7hWugf2rCKzQe-m7(-7R)oUO*+dK5esN z+47;$EBmqZ{pQUSGfQ3Ee(T<<{hxf<59c-&DyyG;vbtW|&rQwRwm>bTR4V${sS@oI zW)Q_wo6KUIBA#zT6Sj7F={yZqZ}qM1>SH-?pXow+Oo?ZNdjF z8tl)e7$Qo3&wWf9OS*=j4n|Jwqmrs>DFTVQ0ce|euJ(UpG}w;7q1*hks`qelNv zd46>R&MMUY3q5`VYW{Wz+Wde(#->1se?UKiL<^@C5g?#bLvY1DPz=dx1i?}!)$P-g zl{9StLtmf{K|tddceZ*V6C~?8bqWnYP{lr@E|TdO4!c~aqjGf+04bO+HMl}kUX3$mVj?2^oa9DyG&kt)?1#Y`_O@*&_gY1!b8niM1&nI zX9;{g&!C|6-QY~bfE<;4p_@kP1C)ADkekS&DTjL z{~#9D9O#hZpKtH_9UzMz7Na~AS4cx@?&2;ZMK2xVUM3Y!ofwZt7h0>y3f1gH#ur1& zr|Y~J|GdO1R>T=0kzn~Pp&H-a0^cn3Ai*azj)*XXH!Yga%(@F`v=SO_kL$gVB)V8^ zgD@BYz4Qr$@2yCPp!pD93S=;*!@K_T4WdJ+sx)k!I+SD(Cb{bp(KeXe(G&$nOzA}O z)od1~zVq4F3iDVF+rkGphbi;`J+-By<9j6bwERfwB4!BEHVM)&08zRnQOVko4#SB@ zKy?FPh_;9sc~5{FLa3W-h)hvvhiZE1KzdDMx{R|$Aaw@fbs&qw=ejg=alUjXiu8H0 zbjc%gr-i^>w$POVOQqt(M&x7*0_zsSjKZ+Y^O?*_zJ$Zs5I>}JE!F_!gbe!B&`Ez; zWw@*_yi{|9=`+E8T{}OknhA{#f$M2m-%`IHxtWX5WqaRew!;C(5G;;&GB?vQGsB`4 zN>UK|#S@A>wgHjxVQCGishFh(kpBWnK(@ci*ozXzeKz-?Ba@4dlAvVCp=ODH8oG(* zXj6IvaJP4%y2zj;wowdIg1BsQ_@(8=V)In+KrSsVkqj3+y$d-X&igm zpdo60&BvIVS&S4oqSc5;CQ6MsdYO<3jx`daOvO!Dh*jfZx@Ar$EINlsE}%>p!%s}g{DTdl11sL!zHSO8mObH zrb1b%a=MS|XoZuSs)@R&j*6CnZPtLlE!Kg=x8Hw?i(k*juV46btS(~E7fj(rnE@QVXXd!L;lUCcfforyd`?o<4cFTe}!FyP+#$vRk{ln~Au4 zeyZC~M5L_}x|Z+yU<#JH!8<^dWPpHxV=amXunS;78b-g1ji9@TH0xJeyLWvDzTImF z|0upZxwStB9<}97-#fm})E4JU9&*!4Y~a3VAd{dM48@fP?wh}|bW8KgFkQF4-peqo z1az7OzImq}#Yw<$V88!|`@ahOhw#C`{2RY@0Kwb4!RXMu4#&ael8PP-!X!*J+Ia>i zT*9tWxhmYkXfwj&=fcS%y)sKK#Q#9K=FA#6(=gMtsCboWx4J z#7x}8PW;4B9K}*R#Z+9yR(!=+oW)wa#a!IQUi`&i9L8ci#$;T^W_-qIoW^Rr#%$ci zZv4h@9LI7z$8=oBc6`TpoX2{+$9&w!e*DLP9LRz^$b?+ThJ46~oXCp2$c)^`j{L}w z9LbVA$&_5lmVC*WoXMKJ$(-EDp8Uz69Ll0R%A{P%rhLk%oXV=a%BYRuJk8Wh z2lqJ%*Idooyv?ityH}LG+#JrWthj0W2h4W1iTh-p0EcIQ&T1gHWT-@MYiWI02JXy6 z^(@Z%oG&W72mr*aAqT^b;LTuJux22zRO(;*YO!qq&?LIg5Ir$vg~LP?c#Jhbt~v?8 zdIqYM1`{2@t98+Y%M&6Ukr92;{PMhB1VMMSARUwneIrPrE3N^ZF+!%ib-6}UQ93f} zV~B*(KrJV?)k=yEZLJn2z3d`Y}G-^Ds@ozTBG*95C^ zUOm`YveCU&la(b~H~U+=Wq1NS)nMv+jcu#rxmT&ATb7+znVnW$rzf0ES^m3`;<*L7 zC0xLY*+^ZIti_Oy-7rf@*s$#&yVomPsbi>1iJF;lSV~ed%19d9x;qVG!uZ>_tC`a7Z>!7}e)BX2w9EO(D?b5l+yBQX;vAx|svbRL2X1PYW?Kz*l_DPT}p!sQH zcLtTOWo3qFklx}G>+OZkGT(Dsky4$_!ZxdX#)36Cw9^@q{k^mQ&D{q6Aj#OS$19~P z*Wmwy zys7ewlaD32V&Gd zBL25y3?9>AvY`(CMg@&j)0J?2tzg$zr51kWQwf2}v3$>1;UEs(z+0GanVDz);m)0< zQC{Z}qPU1^oT*KS(gL+6veiW1Lx!k__dK5S{Ii7qw1`XKyGgb3?Si6Bw1&%M@tNl_ zPDX>p{co=B}$qteDpZjeTRzu5k|M^hb-@#(w`< zq|y;CmP@MZ3BBQ=9_;n8w}khp0z8zGw{_Pub~P^9a9Vim;o{1qFozmEuZ1K9+tpRuq&sdR-r)f@2EIM#3qIlr=#{2;;j^CM z;%DZ4bgyi_yK*k(23p%qTGRFp&g$Oql;PcGxaW+XJ%vVP=3VY4=phkL1)N*X8t##0!{64Ti8dEO%ml^BpPdaiTTH*>X^&K<4HGlPWLF~9H@rm{9sYM{2DW=;62 zPZv+ExqV)EeKxauQakQl;KMfQeG9qw&A2A!xL)S7qUq>osOTmL-YiU6@Z7arYg&iC zonxD~#Gm`oFSx4dfw~X*Gv1!VXF&DP`3t)t8Ec2wMx8Bo0~2#>Ji#Zv%V`21OefY2p|rP$3RlqbUvX`sgfzIPO1M`?G|vj7MtJY zmRvTU(PDH|tX_}L?)RKtx8L!3{hr_V{{aI92MG%e4-pd;7a1EJA0Zp7_ z4=g)9r81p1I6q5GPf=4U#0ugx|`v8A`Uzrn-B$H~jh&(YJ>*V)_M$&BCS=jrS1@A32X_xbz%{{aRR zIFMjLg9i~NRJf2~Lx&F`MwB>_VnvG=F=o`bkz+@XA3=r`Ig(^alP6K8RJoF6OP4QU z#*{geW=)$napu&ylV?w#KY<1nI+SQpqeqb@Rl1aEQ>Ra%MwS0Mm1zmoaD7yqR-n&!0hu7CoADY15}sr&hh1b!*qJVaJv|n|5v6w{hpz zy_F5+Q?Lr*^Jgu`GW8L>kRQS2*}xmFaZMN9u)6e7k`W~?!QQM{RBMjdaY zkdPgl$OK1HCK1v{9+j{$Nhd5!2FMhlET@YKHU#EGK&Aw-7k+?=;+I2;VbYgux~Qeg zN`5KE$}Le*)6FQ{Byx>2?})RBFY}-i&NSt$u*@dHymQO|;+!(gAqr&^ibGAwG7c-F zjP#r%8_@=nN^?XrQ+#lYqSHe*)g)9bKAol1A}t+q)J{^h6pA71IMohVgS<2kSzo2J z!c}GLQ&c%sZFPuNX?;RhR(X{oRY{o@C&Wrk^!1Qu`)ET8YU#Mr2{*Kj1W#kJ4J6!O zx*hgUFvJaGj&CO+*WGZnjMhXZ;U!brb?ar<4t@U@trwmr%00s0ajAXxT_g;i7u_ZJ zts`J5{Pi;0W+~?8SWo~-mee{f_VkS>nbcSdCD8yG=pryW7YCM=OW9X6Z&AQ^R zUtz*gbrETp>?y7kThTQhW&>d=*MJ)dxARE*+B@{T^H00y2vifl?X}|WE#xK~U}Xoz zf^U4Swxe)A0Dn7M#2a_w@xbBWIB`7Mek1QA0Ed(aHKmB$inW#a{EWwqS$u3X%+5k_ zuxalkSgLzeb!MlNbo%zKk*>ODVxRb3WMlttul09Ph^_o-Sfl<`_uiLJJ^4G0_x1WS znh!O5=BFL#WwMU<~7-z2r4F4(bpLIt-W*Nu-G%!a;`T@?pYa zXheh&(F{`5AQexCMR+9fhEeoY!L*3PE6PxCLnPxA&zMCh7NLoxaA6xC7CR#F3yf)e z;uX5cg(M1&U@$Bq9MKrWF}_f5eoX(J7R^WnE;f-~hEyaWnTSZO?JW$8bfOv|w#P9< z%!g?JBpV^gM@E|Ki<%_mw%RC4E3%N4KC|BpgI27lb?|CclOQcgSiB>_On>VmpDb1A zF<}0ZQnDK6@vIj=85z)*FN&q_m?=FIo{yT%Oxg)^6uo0|&l|zSp9Obm%y1HuR?h^d zGsjsYT-s8aw>+owz&C|&QnQ=I#NY<~SE~oo&z;&^X9!8rzWSxlnzt-rEj=~RX%ba`^aZsX_!zc2T*-%)LbEDuqX!&%RO>w@nl_dq#9@zD!Q_gX0ru*9rFUhdW zDbZ^BVicilddZaDlyEuSVJZI$x6{{YAy73u6X{s`Q$@-RsVfyiQziFQp`wlqs!Qq^ znL5>(mM$1j9ph53n$)a1QKdK?>g(vJD5#coSesMAT6vmQ*pao7V3q1wQHR&9?x_t{ z9ja3iCaA`-Ri*%=DowE(RyJPFs;JzmPlFoMzh3LGqw6XS>H1bv%5<=s#X?c{MkmNB z)~stw>tkbjS2?j&t|oP@200j8PQX)_CB)S@ndi=A#*?>m^r#6JxZ9jj54alqt#N6K zT!gaBpXiK{J&Ehf3|{P_Ax-FWMJlc8?$f%;ZK!Z5s$G&^7rfN<-a5^DKCJx{q0-f8 z(>AqT-^vf8y#>WTgIWJy<$*VM{B5tYDvDk8de>*pbKrgV8 zxc>dsgkPKCLWXjSeN~$aqd3JUW)Y2#tZOAf3`%7^3tpV8DvQ(lVH8&}7`nT5afwfOiDcZ*u+XcTOy0x$tIWBupUmTwSefZLQWaSPnI%G zkLX#m<*UVBl-OpuC1z~Zk;@~dtd*Bsg&2FXbqB-Y#G?GwD%07`Q2tPyMa<d4O=;Z&-H+;RbEv$vtJPfbw)46VSe-o(d8v!&{#IrSjS!T5UvX7hc+tC3P zYF`8o8@|p)cCRa3ZQDY+(v;>}f@@tfR^xiw1kG7Qzl&*ct9#QCcJHwtSUvqQIMMa) zXs@JzT9|7#=upS{>PUXBqhfsFPUpGO z0UmO_+T8!%ZJ*OT-!0+txyqgbmUi6V9lsW>x~hxLHn;iiL3A&-dw*AVc#S7_N6VUm zueN&w3FvCPr(5yBR=nN^Oy0$36vLhVHR3@?D)w4F?h{Qnrz>yJd}1`*|BbHTtxaJF zHy8FFJv`~TJ=}y&-u2$r80y{Y;N0gO?v&>}?o<7FW4oRCdbBc;lYGZD_BqfVN9FUs z4A>rr`3vmIWA(RB<0O2`sNHws_lexc=ttkh20yx--YDZc!1(;shyI|m-!FLLzx_O4 zzn4MDvsP^X{7ue~ZpQkr&{S#9iU`F_>BeqM0fj94@X!AkFwd%Q>7Z|ta>$Z&%z4%< z|Jwh|`I^qpFi!pqtpJe>17*hnJE!?FjPC+Zcfzgs;Emdb5Aa4R^>VP@Ox9%Ln9dO`tLhF>wIWg9%ph{S>a;+v;L5<^+KLn~(We+L6m_Z;GwubA#p_Ct z>0ByqG|6-{@$60!3nEYh3u_W72MSt|vaU}5S}x^Qk*f^F6*lFVn z7Uw7#5it^h(H51C7FCfHt&gva(ddY=1jkMi&1xH|@!x`M7Zqpd4ABs0uMH_`nv75h z1yO`bjR)cJ_!6uR|IndOjTQ$F*v{|=gYfT;siO+0@vy19AWVGlaUG3M2j`I;%grIN zDL|xhP>l?bC=>Ai6ebucFe$~)8$&D3 zoKnX!@Q$u*`nt@?*oMiZk}AQn!#u7j(elayNiEA#8MATygz^M4FfN;Nklg?87t_)L z?`+oEEGo-L1dTHM?(YB>hRi%LFTt$PLU04i(l3wF$ON#D>XHpK$uHlsDs_(hh7vKq zGBMi{DYKF@eF_zSvXwXw@)$CfPE98d4D@PH@2KrGw?NkR@StRqA8WFfFt0Wd5;ptk z!2Hg01SmDf%_AwzN8VA`<_(2>jh8r5-25;&lLCu~3_LQUm~A#&GWo8P@ifZR zyfY^|QaOtg)|78JWwSbm%@4C7z~b;Z$20VLu=XtPRM>Mq<7e23Ow0PGFkt}LFph-An_I< z)Icwh?BLHq^GZY&6h-xN0@n^kLC4}=RCEfo5$_UhSd`_qs27FNMJo>Ha`fj^P80`_ z<`#?XinQcr4zeP2NojN(ft0WAW$GY~FrRcvqcKItPwX~_LGdk0Pn19%Ye9unN_A8~ z*;GE?D-nQ)z33Aqn{5ocs}SZi4Mz_n8*NthRGzvs4>i)j0@WaCYa}fyzxp6g-xFD^ z@K5hRQ4x=8?9|)}!8n%&QrAmlViHs1^!FsSO+_{79Dy`RwFM{Q(A2?H1A<%dMoZ^U z5f+D3JH%Dl<_%aCGM`cq@GKE>716k0RE4!O(n&Y?klw7K^%nmj+J?1Rv7$;9LQMJ6 z5}@@Us9p(TgkOtlcHOG6+6UCc6HDC30U-`9P{qSOb|M}uVHvhz9d;M4)fpm|7#_A_E%stD zHe)q*UtqNrcGVoVZ(7|FTRXOFRtXm@QxIyA5Da!>UA8t3auM#;XbLrE6k%pF^3xct zf>Hrff8;!ol@4%Lo(2e1H1%bX)<9$q7P_?^V6ka&$V{M75!4PHV?pBZbZV1!YYoI_ zc_C=cVLy-7C0)U5e`IWF!EAK|ZMn8>eIv!tCT?qW0E7SaSM6qQ8S`I)b$0MJZuu5h zYs@ou6mDsCaNK5YNg?3UrZ`0S@ zP*0VWKf`lehfvaHuXKg2B17*slV=R;PM_9r`TBEoRfbtjS9JGocUf(8o6{hbt=p(@ zId70oyAXAWmv;BD)k61of3P)A7i=}Rdi_FLvC2k=bWO94Nsn|&t9GukbrH2!O)oS^ zm9%@o7kjt&X?wJNE4LC)ZUyIeL(5lvxtDyg)_(UFe+h1U!?#Htw?ql;sZ3OV^Eah9 z>rqIQer2>p8~A&1lz$VLea$z1tv7@9!kp+S)~f%ho!*pnv56q%(1V>T^VkXW-t&XT z>ov(Unq2sUQoSHj^y_^8=HPF2yh5K9kM3m;~pgi~fwt#@GWL5&gc*F+aAG z(D*D<@TZirjI}s4qYREKQ;zs}GTBIccWlk{7%KZXj#TiGYATTh_jDBbi^;N)r&24g zxRS|YhSoM`@A29K@q*^iJ--b)$!Xn?b2XE5g$a*0+c4Q~&w@y=592du6*W4mkd`?W zn%GSypR?UiSZGJjm0S6u=ur2#GgofyXXO9UmI-xeExDPQB7i$l0250W$&PNHuCt0U z3Qh4qvl-~TF&U9`=X`M!w-HOdbb>Fle#dean-OZA(VR8t8ml=*?Zr;g`GB3Yokcbr zUu==5QE_Dsp0m!How=aH0zmDv3sbjsr?-Q&5IjrRg?m#7H(B`lbCjFcc466flQ)^y zus28O3Tqj5(F>wGcWuM=m!QX4 zoZY%cji9&zrL}?jwyhKyv6&VJcoVI*w7J_W=z0jXjfo$6pHs3P9g?EMFt9V*ybtdm z?~n;2^@;Pbm)jeeY`w!0O)DZHA1Sx^F% z(T}A&s)ekmv5y2Z^D1APk=6fiEgN&fG2E%Mn8QK57A^d%A^CEh8oLP)t(*I;|I+zD ze680Rk04yaPh@2;e8G9#D_9w$)4L#Z(36!o5LXgA-J7LjdLuu_v-$(jI%f&Z{D> z1nw6-RJ*0ox_eu!06m(?3eW#p;3k%z$VaRb&jpz3ApOwYch5I8(1VfCee}%q znbMzekvM%3D;#PaxR5?Qe)IXOKOHoud7w=$=KRLcE&atu%g$+CEAq3Y5t=rqv!-vI zJEc>lT~ns@%c6m{2y6d0@vJkLk)4=R?=*A$*6+J`J)5R4+K0onvVAb}S~`k_bJ~NK z*0dd==d;?oT#2u}*3lg*+M31{Ic4GK11)pi`x%S)Px>01GQX5Ul`M@c+^Qu|-rvjt z-#RMe&(6A9RZX0cnG&w;{g4e_mB5+Gb5Ika~m75E5JvH@TBzo+bzNW^Nj5QvU8l8B}W?_L%uTKW69ko7NT@ z+i@o6v%Tb*-dvNrE-<+1rGDyRKD&axD>jPiwLUxu7fVW^Wm`xOYIWpGm2sz`>l?vT zg*sMA6YT4D>*4>tUt>2Wt{zWmuomk6XyShF`M&S{{_g=l@CASHwZiS$_3H^g@fCmZ z8Ncxz|M4L|@+E)rDZlb9|MD?E^EH3-IluEg|MNjV^hJO4Nx$?>|MXEm^;LiMS-A(K%|Nikm|Mh?W`5y=Z!XdF}JR+0IC9~;#LZi|twQ9X$v)V1U>-~bm;xV~wKBLp> zHM{M8!{h(*IlXSbJ7c6xq- zhKi1omYSZTrmC*q0UQ7vxx%v2*4p0U=IZY9_WJ$;2MdQDY#baF9}h1xH#iR_BO9;7$IH*t*W2IY=j#*72FwoiLjvock%9h} z7$k^rAH#+YA3}^MaU#WvXxbUUNW>UNjYN`l+<*im$#+FU7F)v6BFmO8U&4$jb0$qo z+7t?L<&B8WSKWLT@p{-dCPoqw)dNpf$H@Q8)yV9 z0}n2IxTw%$LyJWza_i{M%uA9aE`2)n>ejDgHiHrQ6P34x$sPras~ffr(! zp@tiB2uyRL?UjT^lyL^4J4_+hp^7WA*rJOs7BWy^fGLC%cQV!(n0JPa=OB7Y*a-il zkV6t#q>)G7M_-a+o%oy)CH9A+S@AKK1e8c(nWdIna%mld5zZJUQeZ+DfK$62h1-K_ zo;79>XLcELfza@wh<%TSq7sGsdcpOSj=r{sT=ecGz4ufiH@I&jKbtF5=X%~!^iAAx)GTW@P&qAApa?w&-t+m%;o2|Cna@(!9-+~*ixZ{#ruDR!; zo36U+vfHk^?`r7{Eb;Mo3Fn6HUelbc=8*tzylLpN2D|2f^5MHGu;1h!KZ4` zYb_98CSQmSW1O+Z@1=RjPfRGq#mBn1iG+k3qnxtJ!U0@}#1_9SMa-%YYU0W_B+UcB+gJM(wX$IUmEsHGnFq~lgnO@;EBMNj{E;?ePnC-%)Z z7a!B0jcPIS=cAu~E>0blk?bXZX(nI1-3|ZyY;L8t6SPO+HZu3eMoet74TE|@rZy?V zfMyaPkBA_z0SeG;4Vs(!DrmtAR>FIWb6oZ&GaBbPfpJTU*!2F?1j&(48JtPs2=(I? z=^c)P*lUs!yoSLW;xLDXaFjv*w>k%X=WReN5AlqZyS2%#3bZ2+rG95L){$--P)wrg zdUqHi+NfwW(jgbS=tY;6kZ?3?;nX(MAH!8|gISXv7hYDrK#dVBd0NSo<|xC-kp^%q zM3v1tm&PvwGLV8?ga6$3!vInd8TymWUh*cQMKXbZP$;AWi@5)wEJAP=e;FA8Aw;$` z4W)s*nILSI;K)IuGL@?A!u6hK!Vp^SgPz;m8k4hynAy-cdt6~GRcOcMc&~6Ml+yBM z$)8msGnvY4TOtvNDb+!tA6K;GNSZ0iEG&_luj7W@`m)K{1mv3uY!syK*G;<_4M^Ht zCOg}iOy!O7jB9(x9ibpX7v3_AeXOA#iP=KT$T4cb4Bs5_Nvg!TlAnKcCqoQhz67;3(%co(Y+9oerHTO)uEW zgBmUh`FW*$)Y!OM+EbtM>?u#Hwup5ihqRzVNU9VLKidjBDP8acI zFMaF#Io_Uf5ct(@Do`s^`vN$?IWw$)3vA#6o2&na2wpIQ8|>f*LpZ_`o-l{6Z09@UInR3DGoSnH=RX5F(1IQ`p$l#3LnAuT zie5CM8|~;vLpsuuo;0N^ZRty6I@6loG^hPw%}#?l)Xi|Ns7q~XL-5xN>=Ou4g!XB` zuIA~ue8#Fd9(X!BhpQz6y$Hat9i)N%FVk@ znAb!FlJ0+7m%#g+ZeZOgaHkGxr)aD;EmiXC!^V@j!=&SIC#Uel4tL=P*IaTA4po4I ze3h_EC#AT0>`E0awp4p|?nd5SMzR`sQjn$)N6x$53D4waMWCcmZmI6FJb2L#InldF zaj2ClRmL5-#;5kUrIRS2Vf5T)x_9lTIvjjb1DMshHy@~LX6Z-;J6Oa%DvuXE?K=Dz zB_kNoYnE~{R1|4$LA$_OI}>kimqP!n>%Jxmp%ga?r0oM$%F@4&H1CM_yI@L-ceO(v zhkNuZ(_pC3GE&d-er5W{!Oi(dLU*a=OVuw)-_-kPUi1X-=jJ0B`PX-m=F`MtMt8nV zxxW{ztRp$xZ(s5#3_YyI!lL4%sB*^pFsXe;)xC^dNuZ14t<4Y(T z6CUh&)}HmIUur;AZ~Yj?QT!55I`rHBMK{r1e(?8EYk8~A)WTfv{!NzYdaHQY?$_hR zltNfY-pt?erOj*wo<{whX(3;~-QAl&VBxio`&l3tfe@1X!(;okHK(p6my5!4!CAJv4)_HCO8R*wkMM)v8FKOt2w zIaU^4%nBJ43T7Nrbzl|JVe#}|0pb>H8IlAE-~*DB>LAj+iIx6!LHxm*-5nxHZIa<3 zAYL%y9wOpIxs+?+P2$zzCGyP~o{0LHAJ#P-41ysFBHUt$O4Za;CvF{N$y517))tOp zR1MP$onY%>Vl8fr5lUeZ&E5{mR1h&=TvZV;CZF(~+{yKiR}GO9rV~~fpYtUlIniG8 z6{GG6P%hS+En*|Sh++R>ZK4LPAH$)F;#}b`3Ch)oqc@geIbu%5o!%<8V^X!?8xoxM z4bByMUpC@n#yC>j&E0J&P)U6jtF4r9Ar{&hRY$^P@zh8bh}S~u+mE!t zHHLvsj@KCQWK063m0jD_1!YkhSBm9Wl4V(%<<68KAH3UHvgM4KBp|$HTgoM}?L}Bl5;!@fA=QF-m!5WJPaX`Vu6P9{5)W*XQYJh0{-e59@|*H483QTFA0#ldZsn@|BK zA@Jr%#=)>DL0o2+Z^}$Q6{i{uXC6f77a-^CDCci_+Z5Ci*3gVfAb>6fAqlAi(M*dL=^Fqk zi0XkMKEj7KL6c5tkcOxq)Tb$m=O?B@EY?Amios1WM0J`$<$Y;-vH_U>CYnCim1-xO z8iJa3Ug}{Ubi!$Njscls8$w0bBgpBRZmE{G=TP3M9sFs22C6k2svP)YUKYztjwX<( zW+eLC%i&uzP3m!)(?XC}r{bg^Uh3OqsuTcb0*z`&zUdepd~ZCS}el5`KZIIw)rGs-+I$zV#}-as}dO@`(Z zVJrVLnJTbis-!Nfx9X~}<|-G=TWl@_wFahn4ePIttGywSo1V?>Fsr+=Yq$E~PP!^h z_UgVa-@LwRt^OZ*tt%LC91N{wluVpujU<)yRVjj_dtDsDx}>#fVo!l%QnmVHw;xf}+PgmKa9tH|iWqQ%yu#x88&6s*mDVKKEK z$DUu*MO@2L-NTKiZ%#{~?(8?_taDM_$5tQe`Bb;jqj<8R$X4xO0d3WmWOErU_#y4D z36xjM$H-^+HUK@DBt;G z?doptE-#PPACElm>e`PWe#AyH@9|zQlxC!qYSiQXZuMSFAVx3nwpv3j?}gf6Mvi9v zP-G=BWF$`S_-3zVqOWTOBoR`i_{whmj$t!xFZ>p$y$!Fn%J1!#Z|)wj@WO7S&ab)F zukvCqtnF{|LT~Z@FYgxLDEnDN^c{*@F41N>=Ll=%CG_lZ)X*x4U;bjm&pm&P5a(3;9c>0o8ah-A*zcF&%lPP=gn_@C2Lvqj7ax35Q zd*TzxW-=*{k1lI+2mLYGVPVz@^CpL=GE=S?6EiL&^DM7%%pR@RL683}?{Op-a~)@m zmmb#S%CX^IUpJGZH2(*~s!A#wY&HXPDq2B zqF0&V2tdaiG-j)2)(+6jK@sAhGG^658{-Q9^F!OJ(9MqjniVx7W4%Q*?bTI9M`8ax zsxCIP@&zOE&F0Kml1P_V&h;}~(Hvdb-9R(2Oo#NQf;8_*bVC1LM?17L(neVcbV^@l zh-h>&{@_UybfrvmOFtp-NzqFu@jk1SN!PRr?{HI})kUX~F&=f?|IVG*1U1N3*o0`s(^7BUs0^VIs9uUn~EnN|P@dUtNB+MPD@o z)3i>%t2DxO6B;I3m(x!}VPT)mUH{)}Dxq5qHdVW|O|S4;Q*>zebwJmp@Zo_s%OfoM zES*m6NxU{UdSD(2Q){R3Mv+>@N-b?4vmeD%=3If8PM2<c~>*z0=PFLH%dl#a^n&w^7orUT;_TA^z^n=J-9jx zjVkiec3-y`JGgI$8#NO+f{*j*$|Ba8Bs^k)8rybQOjiGTuik)Ha)Hxrf45$u&hQaW zApPAq0kW_o{!ywS=!2S#3sd6m7AEWZmTpA|T7$2J;`sDZXNTS)DIqD23h)3*-~q-R zY4ah48o3@$c_wA~We2&9vu}k|xrHiuURx>d;a`;hVRk{eMJ`?vNJ!z$&W6_cj!!8` zGx3#!Ig&56`}%ne2O<@W`IROp{H3{zs@Ah=WRv5M2Qzw(etDtq51vDMp94A+r0@ee zIf-IPAih5-yc?!I;j6S zuq#NaFJO)j`lvH`VRSjC|Cg%&`64RdkmI_r+nxWNo4U3CAGY6m?BsKB#<=Ib;4E&l zAiH-nUzWmwHzA=oP?6x=LU@SpY$6M4em_;a(=zClEsTphAWQQ0AP#XW5p;XvxZktl z8gl3fH+3I4cQf*YFJj<|mlJixzUe5&I&i~F9$w!*`b)7U%A&bLCv z`@|-AzE3i9+_xN2H+Hx>cF^%?+SlOP10nxr1EEqEw9S$9R3;lv!>{0TlS|Xp*$Z_q zB7SI=>fgV8-*a_j2mZblzS$?H*k|?F?_y4emfqjx522OZ`#Pw8e&Z*mw)(ZpX{O`L zwM%>=GCfoC9@Bbd_uQW#Mz1@pwBQ|5;!@lYVI#c^| zP@BH+N3@!6(_|ju@iYHf4L|1F74e7FTai6XZ@#BHA?mAnSiflNqvy9b_c84U)49<) zM!2~Pa><4r5CjgJL%?`MCY4KOQ#mX;kHn2sNQGjFMr}5`q&mIbZJ>G_53SNLv)VQr zp=h#q4OUCjUGDr@ev3--TOv$?Yuf*_Gc%+UyklyddUWKPd}~6)tJ8GU+GNYiWGpPZ zq6|F)BmH~qY~@6SWK=D6l{1CJvh`F%$~}|pO9aM6Z4+LLeYFa#HSSvmZ5E{_R?MA^ z7F&+0-J6c=j+RZxD1${Z-eP!3nZ$+t&VhuOQWczHAzj9KiILtrfV}LD>Q%7h z&O!iC`K)lH!T|tFfC?pQ^gz%-L-`Fo+Ef-2qEnGTed6!1O;sycnKlti zRqGR{Xp^3u1$F7prB>yvMdJU}ELb#n-4=zrByHZgeSa2K8rX2zw^9Zl@wzjz+QW+L z>ha6uv5?F{kxmxUHc{uEl|RqAET(g0vW^?`MI%};*Ce3VY!0CJ_20yiNC&`e+br#; z#P8b0Y&xuQ&gEn$daiovUf6P?Zw0JSuI1~e6(0{5Y->s+n z^6bTJGcKwveOBe*Ka01{F7`%4t~&6zN-wqWzQYc{&7|Ay zu_;G|%(>~Lv4#xt$Rz(2+C|4Go1|){9E)Vqq%2*D(wuaz#0SEM{^4iLdn6%}%rAjV zXi6z3$|OuJb#ZgfnDVs9z&7Q?P$ogTMDj;EeJqm~KApsppB)EBU$Rv3N{xN$w^?H zn6=MSQ&D4}LK6+v*I-@^6hc;*4T;y4dL714JwLjm*o~HiHdp=5%a22HA=}RgpUREU z33kzax4Go3SeHM-;x$)Xe%3vV-E@`0SKZw5z1KXu>SZF}85IW6-G31#*rI$b?I_{{S?)mGS^(8q$rjxGC?7@%zTkpGV_FHg| z53XwI%Hd@j#jK;yd!f$j&KzOGInTK2%v(lTa?MeGTjHq+=gC`2@k#a8KecVvjYWTb zMt0bbrFQmCOKJ7n+KbOp9zbK|)7vv$b#|WG;TiQB-EB1%m+8ZeR(C+##NPHGKRw=f zVyn+xdpQ3q3Z6@uVs9RON9BLs{OnoXc1T)=6{l9{spZ|F1bDsv5pO32so%1Yhrj!c zPkuu3pZ)e!Do9CBf_O+$0x#$#Iw%i!%qtbH2-rSsr7w6L+~6uE#VBi;(1G9MACdMK zK8|QhgC?{`9=;$!>d_Dpt2&eeL)ecN=8sf=@E`!+ho}V#&wC&I-MG+`nH&9Qa`3TQ zT3kelDoTZBaJd?b{IVhXOi@}qRj628)qg)!4M@4!OU6hm#AdjQT6n$}#h-_VHIGH9q zE>i!J6WbyuyC_C8!i|c$`PhxlIGZu9Qi_;7Wf>poNlRkPkcYElB_~!&VS;jXn`8@l z23bs7+R{evf}=5|+00ii^O$f`&nKPvAYLMqnbqv0hCV4HaB8!hz2uN8*Z52aX+tat z63r@edChIsrJIw(n1*Hvxom!uM#S8tiqa*|f%Xh*^Sq@w-B`=~z*C^>)TI@3mP&3q0%yqbfz+$Ax^=S z1|IT=rc?r!G-}FIkPOD+bf{H==~1z2jF&RC5j6k7 zA)J=lwK`mNhZB41O_inAvx;@A9a(D*ztRmFx)rB%_2^ryw062$B_HniAtBf+{LHM!fRgk7OcL< z@op#qc3%3HcS++F@9D1jUFnS1zR5+}eNorA>K+)n3ig?T4;kUh!FRuXbFlw<<(ps3 z_04qeOQ0%@0h7|2{3Fh7lUWToMFZ61DciO&XM8B^HD5O(o$Y5e30Tlc~@ZYW;%!{R7Y7s~^q zGMY8qFu!rx##)xL&`M|IBolaTo^x`K<2E`s$GOkVZSR%4+&UQNIm^cdD);m%R*v?r zsEs}ZV9(cDM1v5wQiUYic;V(!T)gR6UKr zQ#V@FQbiVQSSwcy?hnP#N_AKz?WZmQaZ)Q}Qm`?FJ!S8DeV9Iprw#u!+f8H|h}BWH zusPj7X^%wOVsf9Rz<29vS6Z(Tnf0)eEm~ryyRP0QcNDXoZHmnM+PQ9bq*cvoe)qfI zB=UE_1wL?s=SoBcM|i>&zHo*&TtWe#u)`%jaf(;m;upvGaS@tv!bP0pAFnjPK|XSl zSG?OLM|sKxes7hx+~qHadA1N;!I;FFeZ;zj*(~7yjhc(P`r)KY7Ym z{IG1nu#?I1VU8>V=}IFI{4 zGXcSLLP5T$k(PSFqiZMph=dhXvXYBIj_bfm%fSuw!5{ww!XN}b%_AJlt2|#oI3pZ6 zB?KP!=@ce}9}A2^*ik(4nI9=kI4DFoBGeZx6v8hA!!Q&>!i%n%GbqqYu{Arc792S? z+#EI|5EY9eHv|hhbUHPB9m-(3hp{@+I>U)8!!`WFF(kx7G{i&vxT%Vj3QU$&AP7es zI%hDH2JDtZg9&|GH7pdNd}Fpoq=-lCLQllRTnPh7aly{}Dkzj8P#i@nfkY6xL|7|0 zMbrgG!~|As21L}wUF5}HG&l?y#1-=u8-xsZ$~i}JjJ1n9F(5{wX}TN{yzHtw=up8k znlR|=!MRyRY81w0JUHIyB%a}%X}k^Xa2sZvCFB2tI$$)x!IO`4G)7;P$9bejUM#Ck z1QHl(D%w&Lo=X;IOF1jlo_*9TZksL6LM^4Dwj5qa;c}+p?)V zF_-}`Q3J3tlg6+7Fgp86w+qWD!<^;H!{)%sKP#y8x|lSK%121ctQ<$OOggz_9H4|4 zy@X4xL@+&UN}e>#!$eH_E3Pq_G?DbHXHzOqNXc#gYdLXSgvFeRmGCvL8X%IA%uoSM zf7nce2(`~-hmVjH+hRbwz^aDCMQs~R&%(?IicHQ7&C$Xv%2XgPY0cWo%*hWCTh=Yo!?}1lOABGF3 zq@Ycs$Xk&tptHdq0)>$(Aa??5lJpS0R&G9kt3P!Ii9l}IP^KzK4)5AaYnMPF{ zJOvu6475j0okVR@cO0<4)YLakFvW38NL?>dwJtXGQ$wX3N8QxKIn!5#)mRn1F_pPq zV-{yf2OQN1;n74ubhjyJNtA?0A?>V@!qv`WNL75GVSQ29WKNGX)?&R976q$gwNf{s zD{s(J2udv{ogf?9RhCepUddKsE!UX;a;?V$Hgd&DC&b9W5?5K3*LkH^$urOG>{I~h zKzph|0JW!o{m`4c*E*XdSxO~?bq;?O!O7Uyfz_vd-O&C>BZLjncid0SK-lrr5Ae)b zXKGlB$wpRM*oEp1`|zd?%}`%D&y4jl23^^PwNMcq*$7?Ol5p5lAQ4xp_ms)!p6Y-JL61S)5W`c~Pg8(O-p?Cp?}o zUBnZL7T}`5D-|L-Db7}8qKF_xu$?6-9a~M?)?eeIs6rm-eYF}=qU^=qsKk~~NEK#ZGS#}J%@FY+D!j9W`Bg^GjveV!GZIFnysMA>B zn6+4T{EnO5SOC4TM^fO2YhaTd;QLfrmpu_U#NP=OBh)zH3)T)4T+^H_BnYM@|E=E` zmf;!BH`+ncy5$jlL|z;JcGj1Zop2kX>`7Ye4dPmz+vp)yy3JbJ)KR@1po5e`rA4A= z6(1(u5wr!BRQX~0x#A{<-XhjjJ4vGSjb9!%*Wl9OPMhK$rr|br<2N4FR*kQbk+Kro zGC5VI7Q@ws@M&PoM<3L7a zLZ;+Bj^k9-2FBInOZL=44rM;(&Qk8=Ohx5DM&-R}=1~yHZWd-^?q)WKTWXcarFG_bW@cqcXI<9keV$*R{hRUh zS$zdqm>F4%+F1`q+<_LGLGev#YSUoyuiH6uko>{O|X#Tiph^}ak{#Sb%x#y>1%K-Rq0-uCU^2JYY%?%^ix;x_K%M(*TR?&YS}+Gg(O zhVJN=?iuE6hjF>;PDWP4Zs0?`V63?9R*LBs@9`#Y;;ZV{K5v~%@A78v_IB^U+c2~d z$Mv#9tg|w$W3~FWxvf;k*1fQ{rns^jOg{wh_crhYKX7s`a4=Oof~#kj<7x=stFgXC ztDd~|ZdL@>@D1nime#dqOi+sJ*l)zaW*j<|z9VZSalZ@k3hwY1hw&J1VywnF-|=C& zjYJ&(zv1hPa2faUAJ^T&-Wll=^3u4QB3Ek9IXl(9GtouMwL9{*iyCDd;ie;UDc8m+ z7sfnYTr5v=pjmPQL2|>Ua=sYzn+EbUS99LY+l0I-+x+Ind`VH{M~(!@eVFEO&Jo5; z$qH|*slan23g^k1NVSSZx2{QvKsOy4PCYl`VDjGU{&+oQ7jViTt4O;O&L6&^28dEX(&dGE_x>|E^18=gPe^ z)D3&&Fe85;I0OQK17L_87LQ4!QP@;Ims4Z(8OS#H!j#5Td&GO5yKDW% z$8)FqjnC`%{J#GW7$`VMSZH{Nn5ekO*y#8O87VnQS!sERnW?$S+3ETJxp}Yw#U@Ic zWa>0JG`fM*`g*D=8(U`DdYjq=`{o*;+XRfdT6;yzc$;;ci;J6dJWR?w{Ve)6-K=bF z+f3aj%uV~dOw1c#4n3=t9^LJ{yUpec9>zX?YERFH9i3<1TTk3MarFLyI*2f#!i5YQ zI(!H*qQr?5D_XpWF{8#t73!4fxF$)F3P?b5Z?&w-SBMrMgAawe)f zIa@NqxkaZ+kG6OQ^$E17z$8guqN1rKsUWE^iJn0k=jBW&Qf<04%G9bYsCc|K=_>Rj zOG6;jQoRDUY}&9Pk6t0WF|XdeeEa(S3plXg!GsGNJ`75qHkai8prDJF&)z^^#+GWM zj7M=~eDTyZ(g#d&6odCX_v);rAZk{t`yCPukFm6JkZa3c*SfSG(q=Rhghm>+pU{#= z=k6_evvJIbn>&9FJ-YPi)T>*+jy=1aG}~!`@uKw(mF=nHN~v4Qd>QU)yw`Dh-Vyun zV&S@9!~Uz~_`%G>r-Pm#R0Nn)T=X#^#(3?Kr=J%2mA4;&a2WU=g#?y0VNeg+5g~&B z-e;e7B92I6i6)+iVu~uR$l@mG)B_K5gzF5N=+DiLN15gZ-Bfwhf?%6SD|;!fhZk@ z?xZMK|7EI(r5r+vsHkdb$|F=zm>)7<-WrSOfNGu*7jw-I_BbTz3@WZjyJPN z%G}Ag4ICCIQcQ-VD;Xx7gh3Cagb=EtBebUuL2u-%N z1^RI`(DzkMbb4;bYBttsyNq|KgJ6nVdAP43mZ2 zc=$e*+Z(yyflwZy<{LYvIFF-AUifisq8oXro^es<{_^8WYs-;aO(`tPs*t@ZqO>%Z&;@P7m>paBnvzyvCAfedV* z10M*%2ug5*6s({HFNnbmYH))bY|i{TNTLsJPlO;Wp$Sij!W61-g)D5L3ttGs7|L*l zG_0WwZ-~Pj>TriV?4b{T2*e-?afn1Lq7jdX#3U-wBCe|*nkKizC`xgPRIFkFBPF1% zkWgG`Ii49tQXfuWaa?#Q;IXWT#x$yNjne@bAV#*gF~yE#3UMPO?3lmMv2l-l?4utg zGR8mntZ(xPV{c%lkU-*#Rr{;oA0G+HNJ^3tc-+`P{3gdi43Lthd!3Iy(ZpQ{B9wX| zrRXM!%2cXym4!&qN5=3OG`W?@fOXQE^9qE)3P!NyP*X`7yGOgci7}CRjyIKj*y@;c zB#4dEoT->+K(ZmuaBB0N(Cnu_|GCDwEz50RSdh$+)~!N@M4(FPS=8QGx3Ud#YzmSk zf6ykjeMB@-2<22=DmqMpQq(D3!<0Z5x>1IX)R6#fsY_q_!^Vx1a~B(2HHp(nKCKRQ zC9@~?9#+#o^z>giWzJAh<0d?5vZ(q}Dx6Na2gnKTs14&7?5;^Vb2jy3G`*)VVG7o; zinWFCF;!bA#XBsXHC5c|VoA#XdPcRv)t1DYE2YpHx09%9Ex{<`AJ%G7rfn)yXM~!2 z_R3aYoeGSKt=Uz$V%NMbQ?kSwt7R{XSro?8G-|?}aoChCbKw-UPW@?Tqe=^+0tq-B z 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

7@i(|t2d{5i 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%0D1W%Jn$qkU%DGgBHYvuba8r&%MI{ni?7 zM*;Uca=R6`IdfA%cT9G34Hvs5$Z!`=b@kzPO?$iL5?eUw)oR~>Q=!35fek)b02E?i z72$>*ez*>T&kWTKh9SNfIC~sr>OHGI^y*Qwj=I-> zxjuF4uf@(RY>2%c8|{tDPMhtv-F|!1wc(zd?z#n#n@qgz_F3<}{r(&9zy%+i@WKs0 z9Pz{zU!3vA9e*71$R(eg^2#m09P`XI-<r z;M*m1VzoUm3ksLpp_*oYchM}#CS(+$PM!|zPyAago`FG?7g8Quw9J|tfbnNURI zDDi_)l;RYr*ekg?5IQEz;N4!Lg{#r7QE?!K7G#Kq)S<2`DA?i`*968RI6#73Jct^} zP&PVpF%?+EqmJ&#MkDo6j8_~`>XjGlUl<7q=+R=^P)T8#aC``fPP>wbfpTj%iK|Cd|ExdD=@ianOPHF}J z;MAX7Na#SdK(O73fe8%7>O*5XR3+@ws6fSMJy(=Yl(KV?Pc2vxP$1Pb1huSdCF)&& zS}8Ip|Fjq96zpIL8^v|xGpu!pj20HFku{{Fq>vB;4K{Ju_%%UTk(CB+pc0fk92HYR z0qq}@aakr%wmsX(17{B*6vng`v)KdT#K0`e4!GF;lysix`cmT z1-6QkOdx=(TrM=W2nU#L7`pjd-o`e%(`~|CZ0m{1R+hP8$gFCq>n*fMV=P4J7jlc^ zS>OP1E6vTrGNeo1?OLX^pRFwsr11!j)EB+LTyAnj1>oSC5el=#Eow1ySpO>a3=n>= zO$=OxCNLPe*LChRwL41EdNvHBE$oO%T;j+PsbCsqs2+)c#X&k&xcZ1NV@8XISUgjM z|3u2*T@?CJN}+%Ub8S)lQYvGP*cdfV-LJf2kxursa;i;C@)ifY422dk4oohBlQEM* z8QucN=-5gIn{3nNiufvMZm}hwDrILXSr%7=auJaHW-4dY%`%>Fjq@Dl?OQ`o@sfaghZoXcONW*SR(vL)efN#tQhkF~;*h`*6JBZ2_C)$g=KKRg^5whj@Ti4wN9ZSZWge0vc>W@fjEAWgjR`VZyi{{^tU zP0(Z?A!hehEWM?$Y;r$4dA$s`JpAqKsiM0J>elzXvu*Ht8-d(ToRfRSJ>h@Cdt1-O zHpCqcZMaBV089`vSL{*nZSQj6${u&e&uwgVKLg`*^|e>{O>>JM+~vqldB0bFa)I8t z3n16^(1~93x0zWMNUt<-UVPr;He${`f}jyj9duixN>wkHgQbTvbt7_Qcdy<;)bZET zsyn?vtOR;IO8WG(hn>b_U;94iUI(Q+)9dXPG$HuzBfrZXv}NDI*{|^Qo7NpQd5_?s9(Ak|0pkXs3RWl zW?w3id~9^v-yZi&Yo>X&aDZidWz=})2z4-QUN}H8VXnUl*uP-z=$cB6E*aKboIg#v zDj&Yz%WZOyAAPwGfBOJ?Vfcgi<$9>W@Zc`~-H>0~_Lo7}yTpEDRE6H1SD^dz2mku5 ze|)kcTmKee!!E7%#7(>o1iIkM{1_vVyss{pPu|2Y$u8repRiR>iCv0B32L|JYHvc8fj0WEmU7-Q+Kk zVnV~-aSpd287nXv$B`h_@AU>!=$erooG9qj4E`MCBAHPd&oN;FG9xE4-ii?>V^Ss? z2J;?I^@?Eft}cX5s_vpLRE!5GrEp>F0Mlfp6QQ9fC$R^GQYddMe?~|O&rs}w(4UwR zDA{8t!HOi`=qZCzDhVkKQL&H^!7F<*4)Kg6@6HpeGAOT-HhhvTtr9CmNGk!a5q{Dt z6($zEKoVJx67$HCRHdg1iRns@6HyT_+fpxyvPfuA^L%B$FbOIviOepkaw!#aD9dpsQ&Tlp^Hh#;k46Ey|B9dlv%o6Ifrxrd>yj`1&vXcUV2svE^I{{EWYf}ou(>d-8Z?rSy zg3qyNQ<%1gH-nG|QZ()`BS9a?PEb@u-Ei%$Mi95gLU+kUy@N#)W=K<^ zYL4apg1s;>JHOR|C*FU0aFt@)Jap(hK`hKVku0g6h!5a z_dGL7H3{W5lu5}H0n;=>>(ox~)JquhAl_o|DA4qf@{|NqE`C!V=yX%`^kf9e%6fB9 z(JT!a73G4{HCuyFyNVMbbx#rQykg-|!EqMB%}|vh;36$EtwB(StW-ZPP(PJa5wfQa z5(EcujEcffLGC>_wIHi4O;1wVCiR;nwNg_!ll|fq-R2x-S@zhzL6)JJBtu9()R%T~5GwN?q8^Z`F zp&C{K6+*?_eBl-jK!!x(nFxyHCRK)J&Svd^RiV$!>d)M4kPDLbXKhLlb<72i7U-xJ zuUs4Q>%nXZ=AWxR$Qe(i*hZe(?5z zymnh3?I!tFWtG;T2Eu1+#sT0 zj|U^_Qj(TnhIjVViFj&&(AH51t$=to53rJVo)YO6{Z&@ns(BIVHW(D{*MaBhT}#+|!;nJb zfD~m+gE6>LD42RL_aPU$}-<*ms-Q ziD@=3c&ql*=o_66u!;d9|H_fKXpJ&1OUQl;D}bXK&xIwx%Gk1z9fLO{<={rRL2aoM z6M*ZB)mR8TmLIt|JKDGjP~eSI)gI_r^`b$(_?UG;!i%TkkabZVoezwi(KzB*yyDH+k#E z5s%fYjN@mD7uhm88D}eb$6`5>9f6i#@`;ZbnVt1Fx(P*qCuE#Cm?Q*9#z$b5IX;@_ zMWlH(7Q{-R*?F|tIINjNf@GSJqaDSK zHrk{Aq@yY6qeq&gOFBS=Bm+XZ?YPsxyGoux?8}y zs>hnF%i65Z8m-e>t=F2Z+uE()8m{A7uIHMr>)Nh&TCDF{ulJg-`x>Hcnwb##PXOB* z2>Uw>8){_wW<~|EKUuK-8nPo>vYX~c`tq(Tdt)$rvNxNvJKIg1y7xlcGo~ja&_$jN zu$2+z7DtE%|BR<>btJ0a8Dq|;w8NPT3y}MTBndEa@L=@ky8W2AzZ<;6d!I(=_lAij*`N=g+f9fI ze4ru%PlX;0hKW=%A4#&V=o?|~TfOtvyT#kT|NFc3mMEf*RPZ44T7|$HM!*ju!NsP* z0UW|3T%nJlSSTEo;=#gMSp+Kl1rsKdR|kz^o7*VxW67bz#f-zLi4(3fUhUz;)$7Gy zbEs9~#hWO_ZRo>WC&axY!$;w@pPI)zygMbF$ctQ|??8WUS?2KR>4>K=+AV#Lz^ja^ zMzkBL|8lBF1XHhOF?orHVcLMnW!$UWDzUai1fW#aFlwyoNXkKm%r(`gu$=Y&iUk%5 zq6(-7V`->nam{5skCWU#z+4Pp)5&AYc{Om#H>%1jlCR`EZ})s1yL_E+Jj|zN%=HJ( z8N8p$oQ(lp4ic))H*czl$IIObWT+few>*B0oYh-BHPt{Pg-nkx>or6 z+nHt^xL!jdi|e_Xi&cC*8FM|;6RkyFJ>A3)qo;*wd@cnw{pveR1Qx+8HC(@e9NtL380936Omm z|FRvpT%F(x9ztpF&ouAy7%e2n@(oUP&A2$SIIq|v!_R6oJwngw7H#eFC*aGKNCP)w zEFRK4{?DYG|yY1d(GTLId`AU z8Q$0iq5x6Xi(`9l**0M?Hk@hSTjsVBR?WT(+*URTqaMN*fRt})=H4gxp1=IQDB^1F z;ll8rV&IAL77+F8ya@RSzwUp$5ysx0%06(ZFEB1h?b+VL+#c`jKDy!_A;7iq|Jjc^ z|Ftvq-tq0e7yce)ML5v9>QFBQ!`}Fhe;BQtg)6d*`u;8k z$fjDjVHsT(W$$>TTV9W6`3EofZ0a;_-#burP0=B9&(8bP^%ly%>I=|#f?-8rull*a z-YX4BbCCH*%?zIZ&Z55}fnb$G35v#I&URdiaImR`PPuHMMrR)w%MKNR7=cV(!YXGn+XSI^wiA0gqIZ zV=vdP9KBrqiic%bv3LF)0@_EY;E{pZu&tZb4iODquo&ijk_={?KSz{eQCr4O(@KAu-Kpy`N7t`m$6h6)b;;bTDD;Z`*4JGQLLwILb%R#G zSvFj~f-OuI5nDQ$0XKbGN8(f?eXN#U@>U->!hA~?iA+eXk z&F{`3*rKaeWDOZ~(d5dPGjHzvIrQk#r&F(P{kkQozn2>6W`y2Bivn~lD|Nw_PI|oM*Ij||9Vm}TaU76Kf$zEU z7(Q$e=wMw4Du&z|0>ZNmc({m19xEg<0ireP!DAte@d=ROMSrb`-+$`3Xx2xF%;R7U zylKZzgoW5Az)DfENF#PlI{74&QA#-_l~r1KC6-Q#zyp3z_-5iZHt+zIYvb9-195ku zHyT{vq!x}>UQxj!7H9}3fCF6WLmQh~#6`&pJfMJvX;qO0|0fEF$~jzHO$F*^oNv-5 zVjM@g^5zPT!Gm5AhhiFMeBhKq^97j&9Z;%stxX1<+!VxhOMb@Zb#_cW%a=10yd^65D z>%241J^PGVpKAmiY?wJfCDmzipq53_q!qGo94rW{|1?~`DtKokP2gwgUG!yjbyLlF zy)3|N2p7nvk{t#K*Le*6hi6J2ll0JHGi`5iPt&=E)X%;hSJRzNt%%*b4vj@_hxC2- z(#Y)nGvtv=J~`!;TYfp_;y!hNg!_2}N5#1ugJQ-;KRj^Bh(G>|%XA!hfae6+8#{vn zKaylX?h3PG?HdoGwd;7#NHapeRSVGOo%3EqL7~?mNCl*~V!Gqc5-iW^RJ0zl@B+ZT zyZ{){9r^4sebzQYdww{^>4lpE>>Y+kZd)`Rl*`bM&Nxh6Da*rv8*IJ!Ddq zR=kijG++!0t1?R6V3xQnm``y1@q+&bMm;0|{|jr8NJ4+;WjJ7crBIFHLIv_f95j>= zd&=??ZM>iZDR(^29d!t?8-y*Am9e$R;CAX5Dp9k-??Cx!f5euMiMMw7pevY z49X~O96VtnzR@@;3J^~oVipCZ_%$OOQ9%>r9~#rB#x=6Bjc$A+Gl*D+9|EFcNYcep zY$2bExhNLsVjex#5E85$fHiouhkS%*DLB~SWEKCwG(w|NUsXiU}9+|GFhIBBhL;V8O zX9@MBgIgz9!z$LXlC`X6Wy1zmz#&I&AS4D#-FMI>!ttao78J5jEK}8<8mRJ)9ExQj z+jFV)&9Ooa0uATd3M2(xZ$Xzx|EyXM%T`?&NO+Asm|rVv#`!JF4T7x!3eq|sCLEGtTp|EqN5Av3wjPJS|!qkNsiNx5=V#_^S_tmQ3pxyxRzGUrq* ziRp2|3*(KFn1ym?N|+fEVb%nh)41kJ#QDp1zB8Wltmi$4Z)|+F**u>d=sy#>(1t!V zq7$v?MKik5j(#+xBQ5DkQ@YZYzBHyYt!bRhdDBqpbdEvo=~0uq)TTZ)s(qH`RI|F( zu6{MFV=e1h)4JBSzBR6Mt?OO$y4Sw`HL!zyIZ+F{*v39KvTwZVG%gzv%}zG7qb==e zQyWSUo&;F3?Z%YBEM3!MGal?CJ)6Q* zV`H)!w}>p0VwRX7Fxpbl@s5XaI?wjE#$zt?nb%quTzL})dxXXebc<_UjI6&-4G6aK5xX3 zX{T=Duv6x(z%X|xd*$dYoEV_J2ICw~Ea&O=r<$D*4k##IVCDuF2$k>ask0^<2E$|coqo0?YIRkHQIg-&1bRj&&wg*$$qj-(S-)YkQ#DG56{!5v^SHlJXjp` ztkD0{FiizJ?sKpEMgLp$u+06t?;C68#-_+ZpS5_+6y6fxf0p>o&EfQd?_9++e}}F# zeuEU`S>Hz(;W=`+^cS&R$!f4cs=W;u4JfP43%W#<9 z1Ogc$99O&u6>lKmHaNfn9)Sz6R2{S)qlgiY98V#Tpr1&T3o;KBF$)@8;34Ej1|COT zv|v*-0ccdf+qIWISzh}fp%WiJeqA;89(l-*nFv$spuBV5a2aJC%hG%HrL`(%*F9ve*iUxMDE@j_^nUb$OJo z@LLuUBTO(O`VrLz8N%8Xih?L(iyYA0RD}?M2AhDzL`0+TxCO8f94n@yIVM0$e=aveLSq)M(N{n$t0qyZ0D9%UrT zWkk`HF;U(~PRRL}d(|XN%A{V5o!8tX39U^Q6$T2-P+Td2~4nDF(9Q< z)?!p*V>aAfrIZRAIHe4V2{$gKI-tsNjE?Y#fK`G>FqVU?JS9uUrCiRXi%AgXLt!2>#Gwe@<7LT+)G@+h>fnb{grO1PT#I!6JNTI6dheIN?|((SUs;TAG2DQvau4 zU}+I@$$Y^ljmD{*&Z$h;C4TyjAw*nX@}&;KD7XY>I{f9*O&=(-gP-akQ?`zsIwwhH z7)I!6pGpfxLWA&F39fZ2Lh%Q+w#<*nb^xShPBrVb@ ztj4OWhJuYcDO1Lh$@xWpXrtsrXIU%~72xWt_R}$T=o$#eRW7Lp&0iJqCnTPXeYyju zriHGsr*_Vw8yspe$q2D*>&X<*O`)e5gljjVX|-nObe3mU&MLdMtGiypqTZtv^wuz4 z6I`t5fDsqF(5v^TRSi_#wpg%-K33xMApcUsqL#nj z(j@7DE)Z71MyQ!E5|v8jjR4|ghDss5hq2N@WMP9z(8k58M8=lr@Zc+5m8M)>s7IPX z%1JB9mZZk=E1nqayACbU7A>VdXKBm>P~BZ_JWsTu2~D91Dh6zj;;7Ol6`C;Z+Xa+; z4HP{|Z6a7F4R}>oVG~egqe~^BR6S2b2}_``EeZn79gJ;4T?Gubr|KkUBE(ck9E(&9 zm28Qn7Yxx~2<};uC7vuH+1_mj{Ru?%Z4Mf($AVS}p~7Ko-O_Cd))Q6^ zp!yb!Y%WW5E-HZ*=$fuThyVgM2nqz&Gh{A&@P?1kuG%h3?>&_=DgTy%_-Kf{ZhOS8 zS{=h=-ks1ILR!U0sNinsx)MjSz-h{CE=X?_?1<}D7HG-tyRata?(XxvYUPeE`I7Hq z%orAU7}cC_%IXjM`W=Y%!TVO{`If}E@x%QZMg9Jcd(m%=K>|rxX8w{G`d&i0O)dT+ zaQQAU1IsADF2Q(J5P?GQu+ztUa*#U^+ zBvjCw4FU-#7=LAf3R^H~HA@5MunzC=2!9-pDUb)+4+*!>ZB|DN!!Qpgu@WzFAuL&X zIdT42O6@W+6<4tpe@i$#Sr(I1cyZnpe=!(`v6?Z$3$N1yAOAraw^0h`L=E4t8|T3p zi?JNf@j8~U4X=|O8z~*{F(3D_A3xcrI!7QE!XRToA^$NVC$b_hG9x##Bd=o!KUp4Q z8U{bIC0{Zo$HXM7*Ct!X%4ISrhjOfKK>I3hDWexDPeLk-!YbQOD?`F7Uq>v@#4O81 zE#J&7Pe(3`GA|<<0s?Q1=xvVuawHHlC>V1~Mqx24Gjs&eLwJ**y!zJgc)mWB;>6^BEA6+zi{KU97>*Vh;F0 z?b~r`3^$ZyP-#U6aUm2{4~z6nY_vv-uPT)E2W2#K2nS3%0!ueSO*bMyZv{!qfki)w z5helD8b?kC#Z4!eN>8*>^I1}|t|2saUR9>$&?MdI0-OMI96+_&OsG~5LLtKmtNubr z?DQo_^&V>LD0H zGF^+cQ!lk*qna~bj-XybV{gJW@W#svBUE-Jye1-Owzr&7YhZOyc=lqKcBMV= znT_QAs5Ts9Qb#^Qo6#WzJKrYAHbEYbPo=RyO#dAwblMufF^nZb9Q$03J{QV}FeFW= z5e)bB#x9aX0&7PCRWdiZ5W;jz24^KgY+HhF>zr+yD4QvrX^*!$5mct+klq#EZ3UAq z!px>V-n`OJW2bjRIcDY&lzTe?do$o_QyDh_y!DDYvBV zw|R)2=YimYpB>pbc#4r81&#-oJVt_}ciXAAea}LBPmzX`L=AK}1C|C0(Etja_$#FN zYbOHjJt4A|_!`vsdRIi<=|YfZN>miyvwS!=?VS}kDt#p7HoW&o$G1k}I3$T)akHI~ zk9g-b6oqS#lvfaa8_4WYDUllma1pwqcgdjZuA*~gSliTUg+ZYM zq6$7bLF?6{J5ZrV*rpR^r^^9+6jWjANTVk@VG8=C7s{#k9?4)TMZ^fI4xre(c4 zJ1}PZeL5ND<6b=kulFIR8|LW@ratb@s`t7+c3+|EIrupU{2e<1-?1;oCLK3Ry@R*0fXNA8z zYaO`Pythb@c>Ja=Raicu%Ja9v3QVjYYT;obI3%bG zVi__Q;@99`2vuTL`r8A(x_1u|Nq(U|-|1w25={O!OQMxmlOWMzNYH$!C_Wl**NO#OljN@2$c8M}BeFxcvFbv4?=~C& z>toM0XvE*AKHUco%;0_;^=$Ezb=!Y`&R|W~FSx()`{gwD_%~7c_cWCHLku0q-I1KL z{Ek>MgIDsBo|``)p{+Ln0k0+?76r8&%D7}2Z9GRs5VO&WIh$xH6|9m?0am;8D#7LE zGditav+gFEsFqT#L@`*8w>6PwBQ7~6nzub8l(sz)o3BG6K}V(dGNw3DITOK{oCKMhPE+ZC6%s663Vz8LdI-8hY#ZRJD zJklkk)LF}9)^6`_@c(e}aq@EWbM$oeb@q1mcldbudHQ<$d;EO;eSY-9R3|k6vKd(9 z-yRe>JoRJ95Je4ZOl$~bLZu*)gL)D!e3Y@7by()^2LU!Rj+2<+VyMLv1QMuUEB6;-0Ft8MWu;b zk==Xj1wNZD#Cmx#F61@<}M8lyXWctF-b;EZbSjG9qo@twV%rz%I+!9vX8FiZ1(7 zfVF-)6O6Sqqh!uT7;EAHE)QvQLP`pv0t-4R!D1(I%#1l? zDu8Gx?f>#5f{Hc2l9W3woSSE%Q zuG3@RZv0j1T1Zy-1>%PtMqwG402b@794pnB1&%*f8RT#fRIkr7mZP}fn0a$K8kqSA z#6Uo9_9SO-Dkzy|g?FA1VnIdj8rII#rAP;Hh75S&A-j$o3&@ zn_$5;QDcP)It2`Y7ZnzIutj4NQ4A0Enf9>(Z9W3W(;n!CCAp=9T`OAyZ5R%%J>qXY zERF8<_Xc@1t9!dai(-mG5q%DV(Gex@9s zR)pC<)0vQ%!z+SkaJ4f$CF6O9;br}P2?;jYrI2HaBrCpxm-%Irny18LIseUhPIRVI zor=?!8R4eE4CP>*tWXcNrpO|HeUTN^SjFA8aD>dA@iW&Xf&+NqPEL@{I2u|U^i-0y zgZ4t9dxQe|ex{vvGEtyTsOT6kb_!jVqGug_SwN9d5Ohg!qp~j&1*BOkUe5; z22Rq-iDDXB5CCXKp%AG7XUJvJJuvJPa!nPmY{Xc{PE#D3G#)lO+5ZH#-jy``n}lG= zWev|x10tCfs;NvH$<%&h11nf9BY`GUKS*mOw=HN~Z3L@u5K<}9ax87#N!!NG3S*Mx zAYR1@hUal+1B^8SUi{+RLqylMHqtH;rh7;0>drTd1Saopk+j<)m$kpOIPG^Y2|N!{rg`4^Y<25I4Bm7^hDt0V!(aF0u3S-)v+?lz~^;f5|=Pw zTo{o5%(+yrE>U@RV^}|S>!;od!Lg0z%#|G}1lN00#9sfRp##4r{krk|A_>Q8+ zKnCkJa@=JBm02W51~Z?;%;nvMc+6Bb#+h0CM(3r_T<$Z11AJ;!r&`skKHS$F z6|NzLkbaB=)T>X-gbj=(Tr0q2Ic)*1ZW)=JlBqSFZoPzD8=Kd(es$6oB7#B$3I)kt zNHC}%sN{01*)%Q!N{S6@`F{7+TIe-kbSx@IY+0z~i zc0`VDlGmH#JrDTIn_P2NM_uYur+U@b2qde+1M5cn=UGHB@kM+c9}f7s*x9l5bC6x_ z22{Jj*`|)Rn*%~^2mAY`({*{2z3v4q`;YFi5z(df%D8WG<{GJ@F4Ieb8A#8^q zv_T|HLM2qA4?L$Pq=zS*hbWW=ShBn-yh6N_!q|YZj8h;kT!$}&hbc5K9@2*~T#YcK zmor>KHf%#Tv|gd zf$9K*L)^P^5JYl#M0=o)Jgmd=fDJ{Y0Y>~1O6&^Bs6$A+jZ1X9H#|jDOvQDum^WgW z82hxmVMAhbuh;%K}Xg=0Zx?0d1*p+qyf^g zy*iu--iSwc#KJDgM?`4Hdq9Hs=tdG7pnagnAhE|@@JATo7;+>IalAHu#72pnNd4yd}01c$Xuhp7xp+PF)i+)KVh zKXxn#2XL^8AhY+N3`lz(Kxrz%ln*5$OuUL1$#^uxi6TVE0nm{O$6N`ANyN>>2uL%o z#4N^uax@LYtH2~P7C;Ts3^B$mht(W~*2K)xG)(!BGq8Kj!X$*lL@&kEu-GJ$%+$^C z;7#eU1u+|nh>4iJ!%gRUP5*>62j`?c-uX-o)4p^GP82gvF+t86TTTg@&h8Wr>>Nzr z6iKm2PU{p-#C*DS0FVD~L2q6V7y@$u7~k8dFXw8qM((2M6;L{EQf=B85V` zw4Urs1zk|o6C(@>%x#mEX6re(h_-D52n)@=>^T5fL56U#lfqFRg!7*aZItu`t~1ar zOPbI*35AuzH}e9Q^vn!nYZORnj1qM>alp|CJ+vIn6G8+RS%FX?1qc>pPfN-sA@$J+ zHPTpV(YWxrIawrFy9gp}Hh{QN#+nEag`y1Yxh@S-EL{REbripl(uPTcr1>M2D7PvF zgEF0yRN&DijmS4u)Blu%(s77O5UQqb>(KM6lU<>$AgTaOaV9riQ!Rthg8I=_su&dw z1j~?7ro$_iV^B=Z)YzM%)EXo%nLR_o1Y&@(%g`2AJBF&N09WG(54FxnyGeO5s9&0z zSA~RC<*2vI)k1IqHyBk*!=|8&Oh-^vdjlv})fj{#O+e6$TU3Usip)53ez2~i?eao*{*UUdV6>aCp_ zDVp>>9wpfz@-2e)WnPrJUg*%G<@HtU^3ot1S-_d!=fz54_zmMs#uCBb7olHRC|@vW zS5u>&i>+T|xXDc@!~&KvBe_K#T3L}IhYChSpV{6DJ6;kl;jB9VhT#m95QtkzglF&; zP!b9l?j>~bmZ{nbd9vZKa5dc>oXHp$%fR6zHLp{c3Z{5CxshRJO5N(x5FI9+Nzo$# zd69)%6)Nr}7an2((yxP4w10VFW>Vty>DZBZs+X8yXE6md=HizmVrT&4^$`(US;b)6 z1OFl3N+afkGajD6@Rs)bipHn_LY0Ey`-&`H<0-Bd2KJ*pQsX6#lP!^B9j> zJ`eLv=4RpnTBbu=R_3CaW?rs^`QT=9_GbUZDp>A9?%igU;bwOpybz0lQG5h^9tC|C zO&|*Aakgh*geZAVwESc$V;JaKod z7;$E2f=1j-dIOFYq!9k7U~FZVe(C?h4^U!**{wAIN~T$RGB+09Vqq_tMVzK3l~&4Q z$KVP=)Pfks>6W`NrRM3*ArxkXwL20c_k16b@d(VVHJfJR8S{j!mg@9T>OP|CO8Dy8 zz3Ffo>yO^t%cyA^J=YKINe>m2_-x`Ak!i5rYMp+jm@#S6YU@DN>nmeq(ajXG?j@d( zmQtML!>*s7Ug)eHF9=qsyEN-S-QPoS=f>rO;z2DKC0>{gZPCV~nE_wrc#oA?nH{mP z%;2G3cI}Kkp!S{UVqRaU8X?oRU%(mRW@g_Jdzff`HrQTdVHPOw0B+>ok^g`;5Y$HQ zk6G=sDeiY-Zs&d&=q8%kJ`~3B%AKwieT1GGb?WGUm|K&u5w!*lR{}!56kQsLw7?TTO#?;^alhh-Niih+@Mw38aqTJM z3NPx4#g9;CniL;w=OYN!U8o+Xan2F1Js{-o(X4QIBI>Jjz!1HrN{EB9m}yND=fhovA2&O+r$W>g5DbN@OwARqDF6Jmue zitboW8-rTrN;_!k?Q=3kkw5Pmr__){FEfm8X*i9jv{?m5TL>44C2Owqv|%YQfb={E zXYp=y0l;*k(R3!*bL<{-c&bdl&MtHfhge!0PFMA2YV}7K^xck&DW?WfFZ3F)^k(I% zGD@ickKnGsWMEfRY|r)r>Is%>BxGwVFdmJo zJcZ4M9g~=F0xF-JV4-p^jb?%fvv3bU0{7Js0o3K`>~VLneD^!}@NRpnv=nz`@^-t< z_iS3$@W$OMN>YIzCW43D%h-u>FY>M?2c=#1Rq=#&KX?w(x&Mv7CCTP%_(B_hcO-ys zPs6^hJyl#LDBHA+UE8`Uy1EJXFql<8`AHCnd-rj7p!uDxrgIj=<2gdnOe|q3aG=J(|gmwj#Qu>5)AYR2KeGQRJ zyzkvcFtfJ`rmx@DS_$kr5|RN3wD@t&x%v0urd!4s(~kSLcdgwKfxAB#06$>fApE)C z?{aY9`km~G-W`tp)kCN?)x2@wxW%<_eCO!Q%&!8(ZZaZU@PepnqhEd2r;@fdY|Ky-##I^-{ zEqyBgTG@}{N&tdGEw?%djsi!`hC(`@P=T;kbSjz1jV)K(AP=jh7g;NhrKp@uOv1B#7Qn0cDv_`Da($mz{*4NnC z+S}aS-rwNi;^XAy=I7|?>g(+7?(gbqIm1wLYX3AVuxh_Y6#{Be+-y2B#0EZnTLua! zc(4YKWKTr&f=3aP2zgsFaslLT;k|rAAQBs;hle2~Z=eXQBaw?fg$zMH+$AqozAbJH z3g{RUp{8o~41$zL$6rgK2>GGR*f3?kMNEgPz@D? z&2~bqiIHU2k6)d%w280=wYPK&+I51l>B+Z93o0dJR;Et58n%>x8D1f=N-3!?7 z;6+-APK&JA?LVmD5PJkM`4g=8mZx=*fOv&RO0Zn8+DLF#_zt*d)5?sULleHtOi3kVY!$q?A@_>7|&8 zpec&iXzHh2n_^m>r<|ta0E~H%>i@{8pMu)zG@e=%Y7|zfYUixm#QKP>x#Fq~uXV7B zsy4!wTC1-tA?us6&jFk4tC$M=%@!1$T54*{)&p%z;aDr`x8Q~=?zrTZYwo$|qU#Sd zd9YiDyI-UW54-So6a~C};41}l`aY#^zu5fy#JW)cXavDf)VpuK)ClYZ!tJu7u)|LP zObWe@7_1h>{ysV}D;A3raylbN1Iv}wL9ETmA15sD%P_|*^UO5YZ1c@H=dAP2d6>jA zxIX_ZG&Lx|(TTZ5A93{1OgHWH(@;k(_0&{XZFM*RmP6z_TicxV)#`Ljp94@rJ1N;( zUI_NuY`5+9+i=G%_uO>9hW~Qi&c$#y-ZO`R6S{sA!S~>V7jF3Bh$pW2;+sXx_&D~u z{P^UQS8n;`m}jo}=A1(vIXja4f!`s=XAF8l1X*KYgmxaY3> z?!5Qz`|rR9FZ}St7jOLW$S1G-^2|4XJn7CyFa7k?SFiX6*D@`(IM~nLJv`f&`+are z3mtyJ*;}vv`s}yw{v!^uuzCEk(_gs#)8TKm{_pqi|Nj77o_)>~HBA+*XxW;^08jBN zO}Rr?sTxno)&VaO91vOyOy4&kh^`DyV}jc7AT~O3s(h><1|k31L!`oV-W_*4w~`HRotLj(d%k{KOk z%FoCJpb*u}K@T#ghei}ak?TZF%0QCW6qHSv5b0t_n#X)jMWC^8fW7Rs(3*IX8X}-U zUp^&7wLpOeK+T6TXv)xm*YTZt78tfD{`*gOPuRe>rbABEV9LyqwTmzpF#Pf#frSSq5l z3N@^9^$%Ok>ehNX_OXyPo)IKcL<0@RrBgbO9FnmhQ|!eE0KjZ7$}rj}=*UkyBomUz zq!*M@3jdG^qE=QoYl_kah&>`2pjldrCNdo;UZG7bY0FRzOZe6-lmO3hd$9}As`eU| zn35}xFv#03Xim;O(0S5E(j@g(N67skR9r(O%UU6|+KosVRMXkd3Lv<8@~mlbn+Noo zWwm-Ti)%4)+JbSHw^y026YM+4&9Ya%WJp_Ga+4MCGPn}lJ&=g{TS^BrFbn_HZ)7*j z;STGrr-Y$|g8*>_*UUw#n2BU`DbW&TjK>~qYRM+h<{EiWBD&(bg9~T{n$*;07%;BK zsesAj9Nbv8Gdhe>(Nj$y8yUJvei$U0>{8c^7zrgVQbN$Q)gz#iB}4AA9jII;zBD;S zNdHDt5huEoK|OiOZ%#s)dEpWu6Pd|zUW<#v9OEq?fyPg;F_*FN;yVZW%s3v=vwu5-a_ddvC}f(nKWjBuh^n8#hhE>ycQf$JeG+~4UI&HuVl zP4bePd~q&NvOg>_7!f?g%>$7!`~nV^(A43}WuXMl1@LT9k)|VcqO)s5*Yh~+yy%6B zvL9%EN2T}Gly|;}cghm&oA;O%AnpUwMKN`c4CLrrJ>oV}o|ctAco8qpM9c}kbg1%N z%sU@~&M&m}X>47K%6N7}|C;8dC`0ayrsh1DoR)RRKs0a_J1OcO_dJ8W>L-u9qJ@1*#n{HTMB$GWmgk%1CKY-q3u^+v0l>a|vx3}HD zyd6Wh$U_lmi zco}s!tYjx+^+sCbSGw%vO|-xZGD$gU4}M!w^eX;9CGA+dC-A>W=hYN21s>4 zE>JW(m*s5DFXg#CAhXqbjFV=t5i zVV4nI5#|fG(RCGr8LZVR6{Q+AX9Sy?U9%0)-G(TMr_Atf7N~XEqgeaK0snNT7(D*NDh;4Q!}nESF?cvLG#YeGvnR zyM>2pqlX>SholI6p;wE!C>oB47>q$MeRzm})OkKwc+l5IaaexwqH&aHdWjf}`+|#q zsEcZNjo6rtl_EqAK^_&b0z7a+CxilXM2>4mhb*BtOJEXUh=g6&b6NLxkVjE32o>m< z2TmA<1;UB-ppL=8kEEb@Q)Nmf*bm?+j)zx{hqoCncaK&lW%#&N-x!VxIgSh2W;VBv zU`P$zC>#%SkQJGb=O`xr$c|c4M< zRFIH2QCR@_l2(wC@OX7psEtUOluGF+D`9Ey0*L2hht;qFpnzKSr)Xw!VQ3PClLA|I z0*j;|dML7r1u}maQk8dPmIdRLpYUK%Noe&pd|HV_n?@i}SzTm#PHNeNKjD-~rVvp{ zj0%L7#dm&KIf`@1WL!yqxrLXSIE3F3mu@(RrD2R3#~6TzX5`g=Wx0{F*O*TEP#?mNrn)}l0IhxI|iFpC=Io#9B8APgjWr- zSzG|=Qp|@k$GHtMiFNfT0KutEujvT!h;$+d6cf>!W&in%be5I^Hl0u~oI-h>5*Q8Q z*;nRiiFrVtGg*a?umw1&g@%B10tsQB=>+LHl((sp82O&68K43>py(h5>or$8p)e7V zl2Jf9Z75|%P*DdUpU{|Y$^eL$k{lx=b%^L2y$F9`*`OYppQ#X{CL(Nk*@g;A4H_et zp2&-`u%b?Op*X^sjnsJu`e-q_WW?r>H5W|OCZQDinHD-A^Qdo$SdG4TiazxPL%MuD zTAA$EZkH#1*|bOaVxu!BA`^;*P&$fAN}ysorexZZ50R4Yc}^DSqX9*o0#0t(qC2r)vM7-}hOnJOkqm*6*+IHG~P zFc`3+(o-CPJ8;$0jFp2$YCy<~pp~2d&u}u9_$f1H>lrst3ec zt;E2jIijq(VMog49gcaV^_6|tDjJXpqL%p{U|FnIH>H?qWJW+ltGXbIqOCp_g~-ON z`vHp;_m{p}vL;KW_mL`IAsCPLn+oU>QvczyV7f(%F%p4+b4&;>-1)MkK(jS*gQH4@ zNTF;e7PHxq69=JegNlF-akKZz3b;XSEx`h|k#0V_8gjsnQgK;1JCj3uo@zp=M`0N) zTZAy{6V}xhHT!FDQ4lm?hSlJ+YwKljyOmeyv=%6{bua|Opm?Yx4+ymwCh4_FJ5X{v zg}CvyIC!#*+qkIN9W6v!^rc=tI$4tIisW$sj1Y=CVJ{m13+=V0G>WkFF|91&uV1ja zIx=UYIl9g044`WY3ulL&H5D2Ze10Rl7#DWZr(CCXyLmthwUw&K=UKR*x|zr$mn$b3 z8-Asj1-aV`x<;y~>${UnnYbj2nE&epnv03GD+`_*yQ3w#M%pmQ`@LD|y2mtK;XAzH zC$XP8rN{^enp;OoCA?R{YSBvt^m4wn1-@AzzE#Q#&TEa1d%y^clt$q#&iP`%TTY9> zz_G(ZedWL#W`;!?t^$--EI!6UT7 zCY-=Fe8V`L!`Kj&Iy?=Fe8+g4 z$9lZSeB8%={KtSC$bvk`g#TQ~hJ46~oXCp2$c)^`j{L}w9LbVA$&_5lmVC*WoXMKJ z$(-EDp8Uz69Ll0R%A{P%rhLk%oXV=a%Bsn zzWmF;9L&Nz%*0&G#(d1koXpC+%*@=(&iu^K9L>@^&D31Y)_l#_oXy(2&D`A0-u%tr z9M0lA&g5Lq=6ufRoX+aJ&g|UI?)=X19MAGR&-7f+_I%IyoX`5a&-~oa{`}7X9nb=R5hx9<0k>Hl;WC>_%>ZN#ZZ19OCsNI)clM4El@!%yJTGab}I-MDtjW0E-3 zNS)L}oN+c8ev*Stq%ciNJ=IjLjYSOw@#s^2wHUoQp0My&a+D8a*%_`iM^$~+Xl-hy zOHDlO40o~*@?l}_AzTO+OvIbkc%9d@bk&Dum6?_kyFex6v0_}7vF}$XfUDPvz1W+y z)^!M`bJC(xuqFrdC(1w?2cS~}F{Hywy*X98_rZ)En@v_ahoLK?*}dNEeLQ@uu(St0F(X@nRMfo|c_9zF zk=RZCObr>+s3m){2=;3VDDb%(M>=5wiM162rIN27qXBq+aT#e(I>6>a9G{s^03Z{_3zE>#{!Uv|j7Be(Shi4y+D2*s<%<(MzJ^>lvKu z#11x8seTbe={sXU+E#2JQVD8rXGW0$U`%douZZpvuC}8_E-*(eCtq?U7 z>0@pMJMHe~;7A7KU5qkqtJ3iNZoqp!@Zdu6Fs(-$_wOLDGqpBR%E=AF9w{vTL+Z_# zF8^2uC!Z^Qw|Y?Z^5GOlojXST*$pEf4JE$~G@lM)WJot3@-~qg-?l>9}Mr zJsvQ@3tJP^+fV%nOi!I`Y7NLP4I`6#O|9(Sg!EBwP*?9b-ca_P`A*&7^+n(I#G4)e^N&^a!PYfm2zf_M81qvGG)mt%uUS0PUf?`(JDscbt1T|8ion(ut zPw=Prft6KT0pm#ZR-1oOH33%a)=z?PkM>Rtjh|77H}`F4_lge*dB68Q5!S6g4UJD$ zuRpA!@2rZY>9c=S+GP3sn)|5h_QVe<5h~u-^#v|9yn_@T!@CRLy@;vH{I%uq)&DWT z>Mh>GH4q!%znbX$o#iKrcnHfc-ovmi6v6hQQr$uMD7_*_2qU_p6PQHoPZ*6NF zBh6@{IqSt9H|D^O?vC=xzWkiv7#kfQAtNOxDJv~6F*7wcIXgW+K|@7HNlQ&nQBze{ zSzBFSVPj=yX=_z0B5{q(a`Xg5yn2nU!W^DxjEuW{jct&PypmRgi;nB|Y^ZHPr5qiC zeVMY0aF4^L+mdURi<+O4qTz_!j|~fn)Pyk2Vvcl+;vJb5PDD0mb@CBhbpHfc4I>3} ztk}iQ-aAJDGx72TEL*sFMBJ&PC?F&>8`881u(qqCC5RinSQLP<$Ur>K8u2(t52s0# z;jqcvHk4>lqeqb@Rl1aEQ>Ra%MwL31YE`KKB4LfRE*t4*fXLB{Ko4Mx^Ud}!9r z*qi;rYC+P(ja4LEslXAd7A;r-Vy_XwTVz98hHR6_O(K@;*Rw@v)ByU6@1z*(3}UJA zxNTj2YkL+c2)CLI&T(vzR#~Gm5fqz?Xho4jC>+I^1`{dkm$8xErwxNXeGd_7z;pdh z3N9Kk5#rcXyQa8Wvoku5XefVnBQ*Gef5S_M41|0N=TrtG$Zl}i2-Fz7zq}t4DSgP91!6>BskI+ zV;2NQp$UtuNO88-06OW_e*X=4;1~+tm&F2O2uzbF;fOLNh{$=hv>|Z8a9<)L>C;A& zF{$9*d>5vMUw>Ebm}Q4it7~M11yXwIha-N(0Uit#Ko=>ArZi%qp$uD>t2Ye0rm;ga z*X_6Aj$7`z>8{)EyGJR=*f99W%x~3l+Z9hbMrBjPga5}1{Eg~}WzKIux-LwzNFBY+ zo+lJh1m4GOwWMW6EXRhRCnEWraTcZ3ZcRr*uSD-${pve&C;*=vQELgumc_%5b;Q-| z#K^sIgr)r*Rf}3b1ku&;Zr=Ilp^skr>8YK8n6U)LN# zvx~GZ64=w&`S|C*@C`#`_S z({{mrp`eQrR7nTNI1{s>sgscC;{9StBP701YzomNBWz*=9>|B3K_lQTOBO4a6cdK3 zs%0ZSiLg)#X_UVNQG1x#OJ1&RGGv2;PLhzm4|emImo(=&(V0$lu9KbYF&+oQz(#1= z4xVU%qR*T|PuD~$k;$tiRm#hi)5=e z*;Fd^eO3vgeM6_7!Kq7iVYAZdZ2t(GAyth!@>FZF;VK%w=(dK+3V z#P_@1t?v<2S6KlMn7{=#@PWk>CF#b}=#p+^w$Ho2?GO7`toyjvwa$xDrVydXoOs>N>$8lc<+rSuS zBUO|{2va^)^KuL1ycozhnh`at%73@#$z9lB;W(hWoU+GMVA#*=6_L$-LPDU8`V`UGdOrWW6l+mPer z^fhY(nXs>o>)9#e*>E$aoEV(aqyx^$>i*rT@3(C-=f)GfwI`0yDM_s_2j1A;xA~w5 zsX_6I5ZSrm@W#E$`4gZV{QHpN2{4!QF^MmG8;jAqjVsqR=4>nMw*(;;d+(eGlQvGBs3X~>ae&2lhMBRM)^BF3eSwsNDzg40anhj7yq!_siwOj z89@lIXvZGF5#NO+=TxWdWvRi`3Lb(c0_9dn<0?oCSD z+rbfq&*azK+nmR@BS}i%xwzcB5`n)bnt8rUb-DL7UVqQbDgD-FavxgS{$9A&IZYkq zK6XF;y%hKb+a#~`l=3Fb9))GkBwy5KMNSg*uNVF)9~x!x!6kml%?{B6TR`CyQI85~4Gt*}4zCps z7X}8|zRXIGuBj0D!vEFP-n7wl`Cn|QoI z1$;mP2A(Sh`!y7o>-v_1_Rr2AtON@@u^PpHDP(?QVf4&WKb3Mscf9#7-{~* zn?GuqI|SI3Qo#)=aSdd{5YS`Th!8MJRR1It*(W-1=IE)Gq1#K<;=0@k z9F*cDio|aCgG91o-tOkwr>_KWn=b>pM)Yw-=HVh=awTt4nlF^K%@<1YDXe7MHvl&ee0?F1yPjn}ug8K~ zlJPw}2wc8nXcckBG)|QiNFSuf|i>;(>u07ZkF=%n0Bet=SpN;OjItk6JEro54UatK1~9YC&1w;V2UCPm7D+{ ztl(4)xvyGkc$45R%w!{{S@Kwl#kZu|8zB2^CcXp1`GH6%9ko~(3WT$20C{AoHCZV!Ef9Az`wNEjgc6DvH%b*xwrHZv zBfF029$`iq&YP|DQE)zhSKh;2zDOQ}W>4NyrLe0(T(wW$5?x{UKp{n2;YS5(ANuh7 z$Xid1Wi@WGHe>3j3;%YBr6A%T!&<_KWTA~SjeKqJW{eWbXu!NYdV#$%QmHcexJRkG zStYMibrf}JvgN5ZnkJ`X`nZx3qeCM1=s%j-=S~BMPqTYS24jhUSZ6iaw$N79evuW5 zY6XY`@72{8V1d;x!Qaw=O~rDtlsPa_qH@@5H9&<*ETLv*A&)>&0!%f&7Ar{ICTT2G8WhFRg9uDWXtFrR3|Bl1$REt936)>2B-+d^D4RdFf5@D4;YSE54q*Bz zm>55eIhu%|1?yUZEy*P4$v0ZzCgm9?Zf^OBFYhjt*UD4xbL)rcevF}0GY5R=C>6Ek zE00BFt8ENFGvd8L7L8raEk^by+}Tn^H4W7Z4&@7H()3ii{$#UYXJhC-;gc@))aK$U! zPT#*lWqj~-Fd7ohqoJ=dDARE`3|4{A&`i0(SV&2OOwd78g(kS5seK}qAL)4P+Jk{l z)GAIO)k4q#dgO1(NZRVc#=b}bK-33nVhzV=y-BqJ0VlBcBrtU>Hob3DJ7Pn3a`7Cl z#=AXga>xo09a0dE;1iGH^NCZMCxAa7vOj*McgFihr(lOXRRXHTCvP*TN!MBT3H2_% zK%(-+hFma4yM09yuWw!H4=dRP6Jtiw-b&)Z4!%%{qyb5^c>6N0KF;Y)A}MWlD|@4T?`@#hNW^zD0cojxU2;EF_ctaEB8% ztB*t-0o$aJ1oASKJwkHc?)sp zl#a}yes{D?I3wNyl8yrMOoHCC0!4GbR^3AOmqM;}+|z>VWm>4p?x*3wB4yZOuAn0F zzT$&DatBEgo{ka|LwVQmk^$+`ub|S32tUC|IGn2zpAKl5KhN`b;qoxw^2mamXMsh4Vx(Mmgo znzQJdxtE$<;k`c@EQi8)=W))bK)gRM1T3YcLcwtvS4-;*d|iHW<|dZcQ+Qb4ctl?6oV>btqoj2 zIWpfperyob*Okfs_03s?CAdXeU!+M{1RUm<^EO&zUI6ByHakFUmyC6m4a$2Sl$;KG zo5w{c-oS|;t7+Ou2Q9Tnymt0=mLbAb+L?Fl?m(Yda8P2uz8%*YO}=8x8tpHOeA(#4 z!A+$(RKColp zMz2rTNJ-h^ve}3p>?l$}fJO_4IK6h#!RSEfXhScCh684N_F*y^_0Kn4-ZdN+J{S_I zS=>G?IjaJpHkU>2U@oM|m2fLxI2>NMZ!80JpB!kzHb+jnrjNo|9<5m|XGdDyvZxWH z4i|#0nOU50ZJXd{x=Uw_wb7;%xc+cYD zRN|0qcPHfEFZ$LNdBuY!Ys6i1L*roYzWPaHd2h4^EIH?_jE2la&y}@CSGR=5 zAYxApMs9m$XqNKfyM*8FNH>JN$0!$r`a?=F3=ux}KgSlb$L>+Ag{-F5yl3RWMOa{1 zNGv9E*P^!V_KBiMw?~u39OY@~2UH4r@B_s)+yu|XG-*a^WdhU_U>egYmnUZ zZGZ3NxcjIS#!2T{F>mS=&WC^VJki`4=?oC zQXoZA_WMduv`&N74Lvf-ew?$WUDv=j=`9^uK$Uwb}0c1lv3T{|IB(wcn`&Jc#A z){*%Khi_=!Xth^NuBs+K;0jl@FCQ>}?;V|8-afuU;n0~}sQQ8tv1nAvIX}k$4};f0 zIQ6^IA~++Hw2Scs$rw^lG8MZ|t_4(4&Hu_%1#K+};`k(_a3U45YFH?xI}8#TaHmAs zEE(qPur$bkwL1i^D7Q0n5+4<%Vpu#_mMV4YET$?wSXXLIiwGT3qL)!48~P1N{MgI5 zVm0Q$eEn4xF=WcWa3z0EF0X@`AcR3@Y{y2Op!oEqM272BGMDur>538zOB#=*RG?H`3W$JKae{4`GXhD1lc$qjMS&6^cQ zI2Yg~0zNwXg!~H>^W(h8yJY!1DPu!; zm}`Ryg;E`uK-x0aKZuob!yd&`6>w?|eUTCtn)3O3l8j}woj2=Mb-w_l)%CS4=EPDo zRDOCCn4o-UHOv&%hGXWOutQ9ZRce_ z>1BueF~nE`4OQx^{H|KiSBG~%VI2;Ioy{=y#9I(24a`x&6(s68@v}MP0_xe^vP5x z#P<^jDnyeR5!`x^7edi-T$GfHd{mN$X@2UIw%u`7H$c&O9v30jdC|6)+xe&Sc3ZJQ z^O2(KY5?!^+wSOlN*D4tV_nKofQ<5=HUp<#(Z+Ty$t%_LH~I&cBc{s-`q2-cd+wRE zCElM8^P0K{M@cDvz1=Q+{$ce2FoyPy;Ly@RcyoUKgQYJD?jh|Lj~JRlCQLl#O&Yf{ zRbSxC#BUtOz+Y%u8ZZ~V8(+?k`mlmIL+_+v7nPoJaZ?ez8(d{!s(Phrz{PC5frdc) zgMK`d80?apXlWSD{tJ`fQ0C}9l!$Z$>dbm-rfeYTl}u?`BxjUXHVuK|mwdE;=!gNG z85FNH5dJ-eMjAp4WT^Enft506&n?g**f<^*=+|`h3PfFh8^QnZY#wXCRfJt`l-K18~5VA1yloA2O+M zM54lERBaWAJ{gQZHaEr*gcxF~N+PoG(lan{YZij^$CWhuV4b`*F^na`bVA)IEO;`w zd-KN?a8Y6mkF9vJ!VwvrxwFO1`wM52Y=D+REKxLk6-p*7PNw?UX1E{%ypS)P^JZfbvy5@t7ilon<7)7jA#rj{ zwv=Pug;8L2(c+9#X&|lyl6MNK_>?J4n46BIAc2OELTq1$AuH@wN<}a^n>ku%TuJ_? z;xvi##%UddT#AU8nMPdYkQ04a-#faDDa4dmE4Cl_oZ9g@QbPKA@Law){!_&aa6dH+ zSvbF;n;6pSL-X}Lo%!iw6YPS~&o+Jo1i;x$)8cLGQQ_zDen?x1T&bN)(2kd3-g`Q! zidiXRR~zQK*7Y+DuTqUb6|ZWOa)7-$yc#TBTl?wUr-Xb7-DvoF?F+s^frYdFzl)Nk z-v~H(Z6y@8NUM<(SLu`vW#41Z^5t`n`EK*%F#|0E1|M1oIJv$!I8;%!K>>3tr^7LCuF9+8 z^|PNe^ldALNvz>(*rc+#a6*N+aU!1*PR%4y6m3t5iG{I#F!bknG@ObL2roA-A9|tO zY;%?q46a5adEKr$eDo$jxfPZ5j+Cg+U2gP6hd&|E=d71CzwNHZjPfVdbTHZ_+BnML zYwNtYU_woy5^&D2g6oFFvG}f|csl(&<;IrEt13=V^)PfaH-7^5Ex5L~fjZ$%q)+&>9D<6DXvyj$)MTSglTNJ+vT*$6aza;v--QYS@Mf@SGmN%&!^bu|P(c zc}3~Hsb^SJ1<_>|j zFw##cm$zyrSPxU!4%z+ur(x9n0x!&W<}#Q_)2M?=`=map3YmAp9!h~cw_3@T6P0Vn zB(dAf-(9!eYO0 zpQ4r)cILN6W0N)R#5n}@2TE5ov*C7q_+GuYvh&36tLj>qDJd_?R_l^PaA$!wLVFFF zO9lNr?*hO2KL?LB4Np`z5nv~x{Oe8@%Y``(8HPff*pq zH~X5A%Hf`WTVn?7&PSLx!&s;Kvx-)qt#G_$hV`<81t_2cMGvYX3xHLyY0Pn1c5!I{ z8eC>9g)8@j8!bgT_C)$EMaTC<=Pkw7_rxHU;-`D! zH z*iw`(9Nh*%(oAA{4VDsn?HPdQAQXy2x`Gp>T}st-p7`^+fGIG)Hw=qbJ1}=v?%phJ zfLK9ST&gjhp3MD#hSyq4!u@YIUrh} z!x{mX{$!4nfsDFccAdD`s)$^L&INO%getyRoA%#N(9?{v8UtNC+!@N6l0Cq_j~>VD z&=M$R*}6?LN_hdR{v|xv(YOBtU%$hlx+7xnmr^PlgkUFzNk^IuJMLg>e!y3LJSr+i zYBokR{w;Gi(*~hUW|vG@mf<1lnKOjJNgquEg)C}q!qD{6@dAK8Zlu?C35|ByHhu8) z+t7_|S@x)YDp#(V zPvNaZ;V4?RoM6L%%tRw&dpeXD%n(c*42w0425QU%750a)$b{e^hn?c;#v1LV3e!uxLT2_x50 z=|E0Z#azQe`dy1#NJiS%@PZ_n6#LAE;5>x%pLIozapaI9*;u!z zrAAg*S4#XgG;~UstUM^2kX|d8VSe6&T388OfylT9IUUD3nGs)RafM%^#;nKUZD7_3 zKc7sE8(M5{5V^vuq!29j6RiH7`BwlP!T0-kE9;LCYpj{vWsbC>wVd40k%u?_w&QW7 zjMA%IS~zs*i+llW;p8^X(0t8a|$C42~{ zyrgSbPL{%f0Rhp~TW*4tzvfFg=LCQa2l@eZ#1w~h0VmD~U!MKnm2v?Y)4i|Jbf)w| ziO!kS#Z^TR5@Wc&Ci~M+O7eVAGl+EgMo*&SrpaG^ z4d!4ayYY_ZsS)OJb!m!Myba91;gcrg7YSr-+NM8RnY~jKk>;__-LQG5U-nF~x*y}H z8)7W`?b-Fs^2*v>CsdqBN~|b@nv6Gxx%s1jJ6}>G%f_vce1!bUH`dEb>2w}>`9AB# z8{^~aLxZpA>c=lysZ*w7%FN2#7g)t&tUy`~+y3A$TL@9@*cfOY)X2P{u`FRjVarfA zSQST{KwiEoAzbx#!*<=->Cw->+_Y^PW23ejyGQ_*OF{bb#!F%(`b2l>_#QJAi@1~k zwJ-5ndL|FKchoZXI&p3|(WzhNTBgJesoDF#*|g*fAHt{L7PEU$i8QDZPkhQkth`|7 zn5(xCUmmq}0@&U2YRfQclAmpE9GmbPkq%f@t=lAdmM=+LH1(T^cv99sXdlVwz)3S= zJapxt={4o+wKX4JyHc7n$Cn_}ehMDFSfU;ykW@b+uI4$X9%@lMdgQK{&2=dhZ4)C; zvg38HnbI+~^P<|r)!B76Xsb;@w&k|Wx{}LX6wh50%fLf<)Te&*R_4EA0k}eMQ|J{L z4Pkg_tpoz5bOK060w_Pb42)BQG`+oyC#jJGM7R;YT)KTqcX9VBwjzk-vcuDH4zU#c zzT&L1N8sI*W(6ZCziAlsyFZLbDhx}?11mZ_LiWeNfT#RT=qCeJDsb9CruQX(P*kh4 z7;(&E@H4pg^V_B-qPvhUQ3vaNj@T(#u z*p8O`f)lN+5=LqvR$$iz9CZX6&tDLKh`1`5K}GMyr~Fj+7v3vG#2*(agIr|N?rXA# zPp}bL)aU;sYztz)46(=-IJZ_t;yA5W%U-xPf!D8;!Qh1fb;r()w^yI3&Kr(k;t~;= z1(GZLH#{=Y`l%=Iqhx#4r(~G|^2HMufWa?P(Q5ryzgJ1#biee3?DVxfE1BA>ZUSy|_LLfUFoIr@MBLb` zwg3xFiKfVCAR^Zt;X({7M5u<*~sH`L{tJ6i=>=!hA;_vvJGH>=- z#wHVyEh@pd2Uct>8@x0%_<<&XboO;{BHQ}7vNq`f0Swc?U7}4bK`ITQ$hN0NNyxT` z909KfJG@L?fi`8p4ZlDCPfyQA_KwXzk5D9HjU9<#1Plt9bd6o9a13(sP-0DpOcVjT zDR1HcBEGk_#^@L#s%1UXk7}IArb)$EC1#@rV+a*A0?e|tX)B5{yN{p2TQs_P%p5^S zD#X7wXfVozQw)X&8}|<*YcOdb+*J!Fqv}R$Jjo(3GdY$N|EqMvU5lr~tCZ3q}dhLn*Zg{wdUwl-kbB)P< zXJH~8!Fbs-`o*FTiDa319O3=j`0lE&+g*b?> z5LK|E>}LZVdNC-4XF&Lu15=>+hv2w;c;Dd2_BYz2c`GDPMEwS%imx=@H?_)jX+ zRhob3;2a}^r*XZ!3=?mEKP*ABRuo+%C^3?>#j=r-qp5*DgFYm=c#^8dU`Cd+dG<1o zZ^-QeL$)WhahRE_k6TIxb;(W2n57y)n%65~UtO@Ss98(-!w~N$Re^)%PwLFF{Up!C z3ws|LWN_8u-|-unqqw*nLbC;IP*Z9?%Fy@2ez6D}!}tULl0s`YzTgpQlu?c&kfr-6 zwRTBMX-0(35vPDZ8NU-P=|%!ALE!*8e5cIgpkvTNz>-!!5hXxwud`|riOri0MmHuT zx_lSpFg5rABnop2+kN2JGc1Cv(%)e$fNV(KoXSbc{>Ewx0QmLI;Ub9z%vkdMaV_pc z+3JdP>m?o_w9^DI%h>dYAWNF!m5ENV3vWM zAOus?&*0c{=DBqFI$sIrkPKz|a(vEQOJU@YNyep6gtTBl2v=EX{+z;x#iWF5Lg8y2c#4LsA_-&H*vrD>^=)5|y ztYk6aPnuZK_j;M|E?T0K(JGcJ8*VeUV8V|~G9QK+Fj;wNr*ShBGVki68SRRP%kvdE zIOZdN7U+D}!Wynqy=39OS$=;(Mp zeXHls#Fn9SJw$A2S9SJW$odUmhB(p$i=9~)WF=+3Nn_h}Nj*<@ZVXB~c0uss0R1@G z49CSQ==r$GhWExcfC_V;9sszc%qxPEunUiNBfO&nv4qBX7RP{2F;Gqi`w@2-5(y+L zk@t$r_1aYN~eouX|L_(ED~cm=!l zeCz`h3~IhSWMjiHxbGN6hfT8bM91Rfr4iZE2Nk!dauU)Bitt1pjHtZ!9WT*K*?_<0 zpA23isP%QqWMuQh|Exun2I~|vmG{aWJ?O@B;TIWfZGHZDr)kB6U$aqO_53GOZ^3Dx zns-P?t>HzZmSNl&tB^_K-b^neHl-*qMm~F;S1C=_rq;Nob0jf zZ_5YCVk2XQV*#2=`caQEiE8NZavxT>ODMglIqE0uP~rU@jW^QIEE#I=5<0lqi=5{t3Httn!GwoLgc_ zmlcD++L(Iyjaw_)bVfsSANKXMgtRmqa4>K}Nu&;;EE|o(>v_UtmV2pjl6qHeZR%_8 zdM1yc&3W(IwAb*r-21Nhi--1L*W>k+N6}*lV!~{MoO>Zbx2>GO`dnPNdojJ3os!=A zeA+P7y_BzeiaM@wAq&x?Tvp6pD{XzTOwOZnmv6C!g|OdF*CW=P+`({VeYtJeqt?Tp zh`_UB1+?i=A13B#LA0?tBIj9cz%p-r$+|WZ?%7--=47w8u`c`I-Wv1W$=PdT<2Rxg zcu34yfN44V@DF4AQn#~b+s4*qcw32oj??w<#`fc|SI>R-*MN|wHFW~JUkKtZ!9<%o zNb-U*!rd-rjGMby5#EFJ;@6&euNAybJVSgvuCZR5dz5v}asf4?33~|pjPgF?Iz4Ww z3s1kH&OVdY;@>h?HV;)S1qZ(Ne9L{@Jo<>_D>>`$Y*WN_EHCdnpVH$VHRHFf65+de zULx*MIq-TgiRr!6(&JI|mraRB$_zofXEp?-!(K!gFnu))Hh!}K9>z{4Oy zC4yWO*nn}P#cpHx&Y832?n*U=b*t5$6BoX}cIv%VII?Drn|-I8JAYq+zpK@4Tt0OL z%v`=g1A~G?Lc_u%K#@^Dqhn&@;u8{+l2cOC(lau%vU76t@(T)!ic3n%$}1|Xs%vWN z>KhuHnp;}i!0jEKUEMvudi(kZ28V`6M#sh{CZ`hA)u#XdVAs|4&F$U&gA|MxPtzodsx;73HT=j#5kIQk8y% z%z-sc)k>Y&Kot2Io@{*?r`ljw;wAH1o6FvKj`CxgDl7|#m4=#o71b_2v>$t6&qn`!vZLi}rG6w+C-Kqh z_bT|?+4hu((FL^+X}TlaGoCfIQHdbMH!7dY|AAf8jy~iRAQ|K}sfA#pQH$|$Lsc`JNz;2@KZ27|z+U^7)m(Hy?{wgs~@=PaN z>c_b@cTM&{u_@mTC-c;YB7{?fGeJOfskS@z45j6eC^kayXI!fOUmQM z)$TB38=YApp`||ZFw13_`6%0c+3qOkV@Kps?)Qh2qdf0pl;iwRLi=(`OA6}ag8vun zvZ*{N&GtM!Da#LMIV~?vwLh&WFReVStZqF$&G;#ddsfxFY=7o2Vz4q}A-nGjUnfJy z(3_wzySP&`%24HK%sFKrnkkhofY|IPu?o{NVN-R{>M=Ze@z3jeP4N;+^c(zvA?X0E z#TAm8OKXA*vtD++dsl_kt7l&oTF8siN#e}kq%r-F7c^TyOy@KI(2Xu|UN7fpu>tRE zwf`74i1nrVdKjPGQO2~N28Rld`kUj;7*iIH3o-o>kChZHk>t9e(qLO{p%nJd!5_?z z=eIM*=NrRn^jSCq4HAqsck|t5U4xSde$;sLPwu4s>;{SnN9}$)mH#vI$DDRhm;q48sJFaHszcwD|)U(Uns+qX9>F>89XKf&*H~ zq4UNH0t8aQGAi_;8eRnxPv#>EjP_yrUWHJ7C%utU=*Jzu^kGmbK-C-VC+zsYV3!e3 zKgs%4xZq>~rq}2I#miNM1bQKMgu);V@EW9`Qi$sVS4S^=9jOsrh+n2K#A0|Ir9W9n zh!gRP-S_&Z8F~@%h{7;;=JkJG7d|~B2Y>iL%v2yP>wfvL@H+H5);k-Eysbsv`3{HV zho~&ot-`1j@FpIlQcOcMHYz84lMo-hpK8lZulNt_>MJ)_;5CR;Dhy6Sf~9AWR~*;M zyh)C`qF~V*8`tf)NvZ!I*fn9eev=BGEaC7Pn=pO3N$W)~<&03Av;f|wkE)b%r;Sb8 z2;XMRMwjxIDNflN-e#^&mh!iaO*#ACX6>Mt363aEyJg;HAFGrJuZ&H5cHHJ%MVI{p zyJmdrU>vyGsQx$4n;|sa{ zg+IkHDm5aM7G-%G$`w>AwbI5HOSQC<)nY2QWBkCawA*6(Y*r>aaJ$5(n@?&|_Es?Cs;SNnkv^&r)1 z3!;hD;s3y{n17Iv^4hrJLu2|>wT-~U+O+ROQ$9wGt-SL3eC9)QxoVBQ-o*NH$3shf zOpW6g<&E|AhgR@ZjkDLp#`eoYTQ5efON8{y+u^=zBi`s4f9xr0&XJ)*pQlljeDimCg4^l!ATKlVIL)df6G z{C>ht`8S@BS-y4T%h?< z4`S`pg-;R0W29(^(wl@3c0LVpz9VT{s_X$bo`wad8)C&0A>_ACRdz7k6rtz@l%&t2 z3Tlmstb%*=KhVMuX&RHtR1QV~_2c?B%50h0NE}}>iZw7B<9k#NxwD>uY#>-V-TXtI zP9GGC0NFSvt|Nh@3q{ZArrgKLBk|Yg8EFON+_i`ZPPms@kXmyg`MGuJlFwXxY;!TA z>WPv_rAg{^bE&}8iJITbLWD1GIaFTt^nYNNT1%zg)TwUg%Tj%8OZ6AkGsBISW$<)M zt=H6<>Fdi%FJ^0fgzC8k>FesKT5Dt4)VYnw>)LE=Yjc_Eg}u@1`s#FRYunU?v)}8+ z4rUv8MD>qb*6Zf6T3g4;)F02z*DbhY?ye)%uWsX=+fUPNJ&#kD0k5yWVX(lxNNQKX zq;EUO>fnB&>8o&&w_WTw@F1hwwH*8YcY+!4u)y>+eq_fU6;}JGyxKpn>usM&y?tD7 z`X;sW?SM0`ee#RiZRW;Ct&d|lsoe*n*P|M8l6-F|(4?8WN2 zntHq5C51kXs`uQa&Ac6oK%ZyhdhW{9|DGB_UsgYc7BtQL{o@CH-NE|xG@|}~lLdV{ zUI{syY7g)?75cl^jIy$;4ppB1)j7my`>w-V{C7lp@m=pnmfH`&s0u7d{esa4UO#X= zIS}1e1*sEG6w2sjP|t_D#&1yLPi5)%c}=?8OI2SLMvEee7eR)aa7 zf;ou|$yRy&xI=!Mg>a>Z2!lgJS3|^~LL`Vor36EL2ZCj6l?1&*i>yMG1eHiuLm!aB z#*#y|h!s`z!xHX7^`8{b>ywf9m!4cnABL>XENfAH+f}l`+P`Eb;ln#mlgQ8bKu}`3Q;z$L0P@;Zhs&{1i zsOc~dvROK+r8jzBdZgT9WTAc(S*p!#FQ62x;0ccU&1oAqYa^Bj7ql1E>irX({u961 zKlKJ4O6@7!K^)yL7(K`&=7uLRlpZ|}j=oC9E*OoTC61XFj9Ju=S^gi`wGNKiT#eb* zH=B8ife6O#>&G5?#~!E0o`Pa_!LfgkO^TmlZOs$GFbOH4KIx#0^r-gq z=(Y6N=k$1zj6|V~WP^-UpNw=n1+R>Z?6r*C=ZyRcIck#3VuQ>~Bog&m%PhOh1x^5t{&N+8wfs* zWjC6o&l}{(9Arln2uX3KFR$fnKj-X_wZ8a46zH3?=l33oP$ZZwI%Hc5~+ONlhg7&XiJH7jH_D|I%jY&5IA zHfxZ!Xo<9F8@1^AwPZz!P8ebbf?MR45KKF94Xb=o$KjxbzKR)9$ku+X9$zZ^@mt+? zS`pFOtR&j>{ZOfld~yfnMu@fiIvWE0+Ej^iSY^S%oe_+z>X8%fwP|he5O5Mq`_e2p z!l+#+qm_TZs@ANnbk94{5-)Qj`ukP8G1W6- ztcqH-?pcxUc@eZk<@&L# z?un|dsZP`>oNfY`9*AG}Ru1(Cjd?7qpLeYw58 zduP35n<&8S9_lU>LdQP(Nfg?*U(D4g6xqKxH~TZ|`)gST;<$R590$@Q286$(e0b{@ zb3_p(8%%cThf^6$<{VTYL;1Mb`_ws@m_Mj-jx5L4`w1OI>-*5p*}<2pp_up~(`aNJ zm0t5~2J0@AeZS$W%3;U1A6-11$Y%gnm=VV-e7op+ObIxCvyR$$>;Ta&%y>Bdc)0K? zM0JgU6jC^T4LJV#PE`$B9&F01CPbXq5qHN@c}-r-KtNbE5+Bv*XCM+MCmg2>nXlQ% z+CWW%8-S@EknI?R2!}}1J6b@7gn7!zEb(I=*bR4+BHJu%SPf(N`GCHz89XyFgPloO_R-);xRCF&2r;vMqDcRaXBat) z%Gm_BI$(EH;Hx*Mw#X)--%0ke5qu{{gVE=Y$>xzv;6}O-`Doxu9g(^==Qn%-ebMm0 zHfMUP0fS_S!_^2h`Lpm-3&{TSlAZu=iE$nnYe+OeXsdy-cZ!KKcq1A>EH>#!{gwP; z?$mhx#CZPuCL$u|I6KuMQ$1Fa@xS?(50s99V|2l%b^+kgAf!x+eDxpGL0xz=UcjV= ze=%NpR|y1kBSbsSisj4(Il--pA_67GsbK&-*l<5SA()Z_*y~qWU@(~C3I7j!XZ;p+ z-gofZlt@rg`rcrK^j3oIu&8ad1eMwbamIg zclX}i`{5VloEPrVcEU$#Lrq;na{xejgIroWz#W~69@W9&_0h2YBR6|B z0o{}u03Pl`^46y>fg9Ox9${NTg@|^S%yn4lrZI?h@``p=7WdqxZ&Bm!ZAfcNr2)oJ z(;cQqjVZ37S7I=WLgD7_>MKFzVE{niJq?oqbvv~OXxVvRqp4XQ&2+F622N|k=giju zqppaedEac*_v(AD8y`&E6e|CQO8(^S$T#4a{(1L)K7!%ffPs^3-QwkdlLYn8Mg~>~ z(<*Kbo^l`20S_u?b~bL0@qypo&qRsEL9K{Jjgtct#b&_T0T+jwh4kv;EzlRjUNb8+ z>W%cz9|1r(4(W=56e}9vsg7JM1!8lev1ASsTDF04yY4OxN^>{(<4z>qNT=a^-Ot}) zYB|c#oF_w%%BhFeGT5s>(lI?VVHH^?cDmOf6CcZO`0`f!_3bxPsiWnXJvN&?x(l6Z zX;@SX879Sbr@&otG>7JB#O82Pkwu1amy>a`kev$f$E<)w348l)|CV=CNpEqXxH)PkOH^k((>!#OR2w(qV* z&JC&qt&7{L80*5`pbeZ}xTDlEak?>49lfh%q1JuD_z3E%0(yC|h|fwxTc$l&1Um$l${je2~1=M_s^2RZrk$&ks-J=a_B}f4qBZ_5Lkv zC(Bixveg{!RbAL>>7lhJtr$*ni^&3OnmK4^cdb(p00m*4D#ESPw};@?X*u|_b*d}m z4Nmxbtkag(w%=eK>{+L4(DjYYt?kd&X@xBYG24y0%&02I?^&lV)Uw&J^?TN7Tw?(D zorX}^ocQJtfu}Qr^*IR$>+~cI#`)aD_Lw0D)GIEA#GUctQE=<@>Ud}J3pS0RhL>>b zR59J==24^6z8tO2UF&oT_7;x$-L+0fomr69Y2MVR zD|Zdcm>X}m)|k7%MB*62Ivw*A1G0{LNs?-hd+%DOeADB;N>Z%v{H`c}ZJj!>PV8Ez zTJcE}!Mb_V`>j*siKIR26bxiTTBlP{PE5&D(Z6Y(>O@mX`phIDtkaat(Sn&HIW;=7 zX;?H~1N4QXGqV}xK=!%JYSM>uS#axgF1v|ub}pw)ihVw}OZnma%U*RPvwNEP{?JL$n&!c=gibd~yTC?H#wP7%b z6FM^QLg4fkOs@bPbwgOEUecWFaMVF5Q>DVFlvf3Q9^P~!}uriCU>Ie#NVfz`IRmb7N3aV65Lvpr}cDvU(1~lGg0U1yvfk1 zcDcl-M_NDb!!rX#w>1HqjO{sZCpx$FQ_*L3SqGa-2~7!|Lp>=|yK9{`#n7(R)t=s1_5|a^(t~7wX`RL%WtOc!zsEY@ zudi1yeeWBI6UVY=ohHh~{rjy`KZ}dS+9Ho*E6Paa4PDkccD40px}n9r8$e~U!^1=AWrghOJN7*!lgD9; zNp(dID?XJaGcN;_!)I|;YsjqnOSsBIv2SkI;FKoUWmtVn|dj_cuF3?0VtKXIh4#jxARdexav)dG<#0 zkA~fP-t6>wRMGH1@-2EWbUMGOBMoVtE_vj`tr`PS znhI{6Di(GGe6mhWU!36ln5?!vBgrn!$m1`Y){02$un37=H3e^-#1{iFi6i6Dh9J_s5@X9g}o}Tc>Xp0x}e*&g`>JTQ|Ck zzOha%_zIh!Crs_E4Y?H)+UegM(RW;XJ5{p$F?wr=YtK5(;2fo@+Ayssy_l>2bm1~= z6GA5n<%u={yi3~}Q!Lx)IfJ>}`f7X5vutZp5wVfE+CAXjyy~ zd|3mG8VF{w1?N?O6-K~V80tLj=CDR$5DB@p6N(ji;<2rl)*(d7wB$C70yal6nBOFj zM;O^0gV=EFS*Nxq1Z;Vouqjn+1$M1dV`hRnv@^Y!XH^bMK(Nn|Yl;Tio$r+@Sk_K@ zVr>{{V-2vE5VIF=vuiXEzl33jqGA8>n)b_4`|QVd7ZTCOfE1)844M#|yIN?E0%`SC z9Ih$}YY|ZL*Wo`*#88{EQV(>bd+Yeb31rqwVO#g~aU#A&9iAikVQcdHx0IapQJmt* z?s{Ng`aseBobUstKtULWVa660na~(BorI)tBYJI|1+Wuj@I!i?o_M)r7&sQSxuho= znAc$CsLs>uBhy@PJ2Cfpub_Al3e#DHSs17 z@}?~Crtb5mUH7J^@L?46ISMcCoA|H<`LGrEaP;|buKRFP_?{5-<-P99XW}b>=~*4< zDD`F>vGsGs+9nsl&=9ZV!5KMtom< zeD~rN)YnGow(#_sE7~|P4HJKt_7^(WiSjYDbt#DT`}~W={0%W_ADaXWc?X#G1z4^J zfGGk=?lRnC4RkOGJafc9FDlTfFVOf>pa(^eSXH2>SWvn~5HG8rPr)balp;7<5MiCh zBUuN@1;MF(!RhP4nG_+}f+4xrL-Iabr+p!%>mlV7p_PK6)z?Fbqk?LJLK{pJ0t-T0 z)vW*anh1+b`+C$mnChu>)Ptx<>V5*Uji^g6fRm#;Y}Lc9 z4bGf|Y4xvC*>0yfgVUTM)7a6IuR^H@buiwlVvOl1yki5Zv9dDj;7`vecLig-IZv|8 zX2QdhVRT+=GMHptm3WoXYEhMk*f4c%<1ue$`k{w{n=@#bW|=qxnRuI-1XNi>!dWD0 zS)^uJ0wrli3fg)5~m`0>8&PRS9{iTJ-Y9z)SVbml{-g zn!H_Ous$W}UWR`Cnl5bj+Z@y_HG?4#rDIY8h zv3p3YN{EJxAR%=$CV&hO23q)}qp2HQ5D_F@p@_dLg7kOlXj?4#AlsU@7#Ux-n+<-- z9`Q;spCU*(I=ea=5kV?SLfzHTjd^KE9sTT%QTncqZp_cv)6wt<5?n{Kt2aTi#%jHe zTnLxjr=urZBPCpE<-en&^X3PT5u~F0#o=;|WFmPsy?FI6(a|7|CAf~( zU8=x9>S+GCr78kxj^%2SYr4y?$Pp2wJsr*QzV7HRb#z8j3z8YgajjWszm86h^rV0E zwT@1^9jE*tEjmRNsiQZa09tPcSZE|E27S`ehO}VRLBpO$YeNr55fP*{xQ?ds$Aa!d z2DS#T|J2tlv$Ut}mX)@B0b7Ly@`^Z4puQCG{w?4G(G6VIuKlVPz*j^oc zz6g&XIdbhl#{=|t)~6Eon1Ki#Z8dW~LsNNM=&VQ0_6OmJf|IZdb~}m01dzZ=#RyYijb^hOPN-9<66T5?Y?>ZS2}0{=FTw{WY*goKJoe2=;%#g zo%`>KAZ@Jd>ge-b(UhNbbXUC4$0Q~9(X8X3C;{OgI3W?bz8;NtB1V^#9^kDae0?D8Yif6fn!|%r23ZoVURH; zABK~gMAMxI#sL!iOt)Rl|;lUb^!86h7SAg@N#Rb1p1e5PVRkMrr(?E30cY*`!Eymrx_8)d5NMO9&NRa&0 z?tfX*`s7$Dp4kqB|EEL2?l&>`GL4rYB*+ ztPHApo$T?lOd|}adZeN|`%46gql$M|M-Ld&RKMi+8^XCy_Y)DMEx&Qjt~7L`$%>&w zFn^)w`}!MQRM=~!5~cE4izdGnL2^;s8YSP2AbFUHJV@9PXre#9FM{;sQ1HFl-Nk-c^v;zG)2<>~ z(ffpa+L|z6zgpBv#xThJX~t+^?rEab$&w{UI+G!#9qd{<(N;@*(!P_o*LA@=w4rCV zWt1YIz1QkK=44siH7vsz&KHL^a$5@dS>a83a(~E)DEw$vjnvWGfm`8SR-bhAiSh_W zSUw?2uN6Fbq;6=%e3nVh5e)1F<4y5Q*U}MAp}}xu?j8!nIh=^zc@t%Zl0f)OP4)^y( zko2Gq2IP)U1RM=j9F6uxkSw8&U~(s$A4HIR1f2bLBS?YH!4T)rUgvPAb0oP-w17*j zip!-V4)K95$q<*+UYB&JOD4H%wty>KLGMP83L&n=y{@oQsB8Id1WCoMX3DHq2Ae7n zmm<(D2kf$H>((OR{(;*jdBmRdwtE-E9RhHtjB+0&_h>@#7_GxRZ;aFIL_Dd2(9sw+ z?iy!3Gyt9pDxS+)o=^eq_r}iCQ`Fmm2p#?QtY`6AFH~d%Nh{YAYhMKED;>Q~!~(^j z>+?qFXv`~|D!9zoef-+=yUKmokUCnLY6A>f}trunV0S4CtjI?RRS+PtgFw9K$bhM3$yoF$(!#b_K z$zBA>^urUljxNjc@VOq8aW=p|C@8of2&2jeBo-73*U{QXfr7tVN7tBy*1>gjL1+cc)_@+lq#{?vOJM}hVic>Rjs2eqN4B{ z9uYR!)Kx^$Y^X6NQypWok1~i>jEr8ma!7|in)^@G(L#i5-0}BKsEr|!r;-H|^c-1cDae~xgA zQlq+o?2eGb9PfYXj%XUQgd(~l-yT3!81RKYA3zttGUMKhaZ~DeGQT*09+QyLLv%;} zq66r@?#O>%xKRh;Vtp2FS2QGkM!4;Rz5i(k(6u4s$xp%!c>w*wdf$Hl{mgnt2)E69 z7#mYjMzV0$dyxg$C?x^GdMDF-j9|SZgd6*D(>>Tbv4GiEu=n35+`c-1B4O`1kpPfP zIVvSmxS^8Ol>>#AtO?vA(Z16i!HGf$H=^JrJC?7zBM)S%D1Ws(f)sAQ=#Kp34xrBD zw4#-P)$r~JW_9en`{!jn!0{*l0o{>l+2_ocb>Xp)Pu-DU7jC-;P?eIS@;O2&ZyzIs z+bnxNC42UhSE@B`>!U3HpUHxBdk@OFJn`_vtIpAEExcSlkTd#p%|klm4E zroAV4{Rup2B<`Z~^qsllPaR+(c^@UakJHc}JL0CK)=TdGqkg1^6Xd0Jmb3a_?eC6| z>ZO(ZqB~M*omPYBj!=bm^Xzs^cgw3`r>ne8ku$6etjqj18CckIS7;q`hY zxNw7aN7#_UE!#i8X;--6&%6kE=93ZN!Kq=T!nW> zN(OmUE1pUS;I*d7awizV4R1qQtbl?#P-zro0Qi>!$4Z>HNZvopMgO zWduEB$kK#8Gi;^vB(i)>3NY z3`BQ?Gg}IExXjZj#TVWkInxB~gfYM&Czx6fV66R|-#LJy3V`9l4QLF;2n1t6z&LQ> z1_cw4TN4Rbr<15ylNwu-2U=4?tf_aqBfAGs6&ogF8?Bp}q>Sxk;0I8Mom{V-0@O~4-2Re){S_5^Wn+7lKzmh){f%CG zb*Q}txr63U3%AS(+nWNmmd4g7-1gI?`)&_>02Y8I$nDeZVXyxD^H12}4;z1^{`?Y9 z{7wD&@4Y?zrBB!|8NNSsd-zkIu>Z;o-{19wbsM4E&G7Nh8C0-~{QT|VZ+pVJZHTYG z3w(eV_U`rL^uO@-aC(U)bl>ga;0gP`QGfoaC+z;)!@m0SFEV_EwNH(jQ@EFtf3^NR z=K*E=uh*aXuO1fIUw>Y7WMTh_`t#2`VObQu)StiegoW0M!7I(5pRn0J0Zrpfzxsr& zMb@8x^$CltKmXbjcKW>9Mn~Y6`g0`S%l~Hm`TI{;JUP0fXm~%UKmX(pJegTa1Or1-TS#UIRn{B3VA zziVIFh=}|L>_-8RKQaIDPkw{J_BLpK5deWt(ENYsQNVt*LGzsnnm_do_P0&YY-k+< z^@5+~KLr5VZWnqqTBjRkxrQCOGEz8r&cmq6ENsRmp!gL9mub@2(1-ME0y7r+hE43Z zrmOQyTeP_>qn-|Lb2^spJW{YaqVWy~Qc=ajZRh@B1oIH6oRmqy z{^TGP=2HOys+~+dZjRAmJ4ki@GPjG#)6v774%M99@~(HNsSbBS`A(K*J=91Y>$m%~ z1_uCeyocK%@xT!j#6@p^rSYetm?9zx|M41Jz~Q{_uEG7~pW+{ViTBpv{^vi%Kl&2? zizMb!0063xq(@TY{Dbh&Tvd{caFfl&XCpHH6VCB7tm*0frSIi>^mOkO z*e+UjHlm-cinypO!WPgZmW}Ct9tHhN8(`lj=&M%!&wYYI|G*~*aPSHG&)NV7j{LzV z2-ybs{h^ZoRvX~p6ZG$M-0C*f1q_?UYip#vTC^|Z@h zoNWAOSds@i=s%ctInY7BtCDu`5&9qc2>pdR2#8o0JJ3P<*2NA!LH~|*u>&1+po6{( zX84zc>;I8phM&?w@PS|e$LEB__s%yrX_Hk(Pc_a{=D%STkW2SyM8bR=TNjK;IN?bO zW57^p#@YuHtH(B1?7)ud4oAlMVR(V3m)=!a z&yEeF9}y_U3s5>^o>f2R0NY%V7x05K&Fky~k6_Zq}<7GEmk{pN# zg=Ln>4URj@sgRm6LLQHL;xQUW$C}d%E$+5+H1x#Rg+))Fcx07#;Ov3JRIy`B6jlqYEZWA&%2Sa}OhO?T)76nvFKqeKJ==2C1Dxeb>pnSAZQ?|APD z!kOsr{jU>^lNnBf)cGm`Am3nHO)fA!sR$xRo#2a2BGx+blr&aSf^RspndVsz{_xga z?n+j`6LnvzE<;?3irki`aBS;^tO=ryB#?=Z4)!W9p6CZJMcMJ3h>HLg#>_QEV=SEr zHSxr=NNj8Ft$Fh=*w)u>Vq976Zr|HXytI8T_Y=1DvR@`W9>2Y`f>qXKu9aMdGij{0 z*-&6~VO3&@7#o>iAwfLW-e%(7^v2Kj7XP-)y!cXF%b$mBjfsi@;!OZmgERuq=EKCyUu+XAq(r1m5&?!Z6?)DZ9+p>|^R^;@?U)ozF z)Zox^5rx39tt$$6X;VJ)?UnAzR8dE8`GJ@F^}R}S2R9_yAIPb8M&g}10^>--zT`@& z>J}dcP_-YqEZo?YPoI_2cqNhQ=}F3<@Ml>X7TD^ul(I?P_jz@z5KIb{nstV z;MmrH<5`nu_3&#pC3hz3Drz&A&I+iJ;F48U-64Fea?~7`?3#Fu^nhleal%_-kkqS+ z_Q$RETlbh@QVkYa@3WHQMnVht8Ku-ogCeRXmfg@=(3Oh9^!+F9X$$h((Hc;ogi%Rz znYuZppsjpt&BZWm>CYkO}`a3!}WN>q8e|Np%*}ZnX9NjN%!N+ zA`8CLYjOj_yVzD0_afev1&UJJibZ8s77oB7P9=NIQuFc#56Kydnrjuy)!?n8VzTQ> zGV0_3_*E1q@m-o9o+b(^Z%rq5$7HaLz5uX+<&h#C%`@PVD?t1h#+ z>~BTV#MS+=)hhdA&T#wUV$kYXtMuc7GK%qpvYkf(Ft*}S*d`hH^Yq4WoH<4yBY@=; zE(EL?3AXgXg5g6*35>0I?r;xFC|#ttuJcl)g{KL8e?6C*xjzshk)&`McNyc z+sBRAEC0mw2Fr?#F%sK48)a*$Li(BviEVW{4mNgbiga|*;y>Gqz_xzcOjK|-b{=nM z)<{HdCdyFQlDphtd>Cu&a)Qq)@zZ9aa-j3jl2hjHW+H}&+Z0-MpexLdUcLx|T`}d# zDB)_$tydlB#&y!Ipxliq(T!l#?IQ@i0|S9=oqz2*8s*;G>#lmm{SA^mfXb*jzK3l+ z9tOv@?qd%`-*>wN_RNa(T{D>Zmp8~)sK8ew z=m=H4?U^LHh8}M{F+WigKZ0?V&=H@N!+!5a{H*Csl$`ypTz`@J%qqtE#Z`i50!cpD zedHh$nF*~I4%{ymHT+?g^e^t3P~f#`Kde7`tQP#oc8qLqSYq8-Mv_1G9Rd zRRQSMI=byNx_wZ9lPtOJZNI|uz!WbX*L9%xG{_?f{Y4OZK$4MpP#~LM5ZPPgW@6GE zqk8-}!QjN8V6-~Fkc5E6!vbU1rpn$H*$Cm{(J7ZT!}shIsJ<=8&|$3p#V;9kmsGQ&<3z*Jq6HsJv2Bks3_97uEXCn^2JT&XP(bP#Wgq;969=v z@TX-lN8p2v(;${5%4!OoS!Zk=OyWqpuwZ(rkFw756rlJ(GMH@mC>n6Bo@_hF^pGln zV?8>q4mS3A`2YX;NZq#Z{=mp%gi#GnQP$j1*GHnv%A+y?(ZTf5&Qj67Ak!@FhGAs}TxkZ%J$Av&TcGe)ow2c;iN+!RZI%~N#7LLxKP_-(8? zAWk9~Br6o>VGx@Ph*gM;Q)59lwuwsu#LK*ja|6UHo5r_PL^qs`*XoZ4X}al}DqbT@ z&{It?`m~v7-k)H(kpM$uFs7pi7{-}FjyOL z1RtgzyYL&>R^6-iuf<+o+QgkF$GvWbWDhjnUtWK?%N~GP_vYM3um{9XU{h!n?Xd@< z*7FPz>;W;OkbLuje9O)Jh{HZ+jj>=gh=Uo#DFotD1aTXHcx*zvX3>SI3jEXx{LKmi zLkfb63W84*gl-l@QWZwe-Yh4_vkgH{5H3tED#Y|DJedm$Z!F9fF3Np)I!mqS{%9eD z3Zt;7sC2WaoT|7|xVV}MT}`dHuJI^-NO5;<@%60YmXz>zVG=mGb&Vjq8yh!zJgje^ zO>4aJ7R7mMeQR&=3>B45|5>?rI;j$IAvQ@J(Xh_*cQQ6i&+0JGe zfVv!2q#W%=IncZuBeWc=xEyD&9B-=}MnGLbBvL_gqk`1DBK<)ajg-5H5L(Fv?8Era5a ziinF8gm=yF-k>IbB!c_e3|s%k{kOs-&jw9U9@lb`*50hBZRTTs#aC;|P}fgIV8?Oa z(i~HL4#W9|zg}A%)^%M|91>p~Q2(|1P;fmHuwJF2{s>mx$@q9jjz|;Tx`BJIWcVB8 zVj5!O>Zji}XagEg-f(?1KoCBMb4N&bj_YFMb*4tr9Qjmp&Ei3-Dv|1Nr6x6@#s)d; zxo$6dc6%JtPSfX$HU?_yzMrE z?RemF)7D%op+&nH84#tdnZ&)FcDtRPrUUEbVaA&sOcot1VI6EG9UMa)oZB7TG@U0z zJ9%$*@>z5Wgmns)bczghp5E>hqv;YC?UKCNC1uei6V`RUq)Tp_lr*hFaj5H(=cSgQMU`9{51pU2k^?1Xu#u5+59%6NE!E#HAj<;giXMQ65skz0ps^u_)tw5YUWJqkiJ}aEJaQ ziP8Q#dq;z16$CV6^@_yzp&2e97V28k-+)hYQq&h-T!2jdmI3s~pc%hv0P#kobIz}H zDoNi|?Ywj?Y!>LP@dyc@JSC@khG@0tF6=qrwO#1VZ{U-kw^!4W`z=A~(N}E9^q_B@ znA4%fEF53pli!Xe2u*sr+V~R2kC5!gFaoZ(yp^c?bijcx|ERaSn`g z&j6}p&e(e2#Q!I4uhN2^n~6ozl4$L1uWF=A5@2AVR@x9XMFRQjD)3B~Y-eO>;M=R> zTGaHc$K-BH+9Zf4IxUfK>0ZEzvCSjxhls zgP#B{lTn?s3`aL15AQ3MLRO-yH>60}bI-B+#Jd^;#(1hRq8Lnh_?6HkDI>CKa9%FE znO15F)L}3PRl>Ya8ih*G+Gj}vKQ4QKaqc>(7z4#AE4{9Y#nO}2*GUPz_x5tzgHQTg z)-R4_a`dT}Z#n0kJALYGoT92Y(2il1mU;&ax56C8k~6Iww-X-RjWw zG;E7ukZNTb&vLag;s#(!9L=TGhH@nmLq1U)X#lN65x9RbfSMDf_#4_izGnb+_!q8x zGJraR`&WM7_Ub>x0J{32c8E=4;ZGc{Dv2LDJULs4Fo1+n$0!D% zHLtulJ)D)tXa?kJPXkp2_1j13;m{11DIPXIx4UO~`bZS@o-ABZ^v&+Rhm09(tX2rl8ha06%-HdLQ)+S@^ z8!XRzDJnUCUtdjT2w0O-R>Cdst0vXoEmb(3v+(r8k-=VT{>Z*Td~WQsZx&jR+pBhN zMZ*AmNI6Jhcev_-bP-C=Ui@q}|lXS;YRS4gNMVBxGsK*h8;Sg12W z;nQ%H)Qfc3#}5w0vk&Pv(l0YSlXs$-)AD>#U7(JgcR6-$UEtc1CTJ~J;_d8qxv=z# z-hTLGT6f8k6JmQ+Q6>N6&a#(h@i?<05}JWoFv-cAf!;D%@gWf)IuRz`cEgVrJE2Ts zMm}q!{ag5C$-?v2ecP+%#LFenI>plWF;AiIgc0z`)2qQ;7T2lEHjIUZSMysD+pC)+ z_+=A`T$ZD;==SdxN!B}CcS?jxw|TPpw*x33{n8v@iqxt;yiqt4`NRF=!Z|)<=J_y}I83+JjHZfZFd{c>(N1 z_ZdLWli>5cb{2d#3Ie#)Dl{0p_7@S`t6EABQnkQO+p8E@${3>e1RS&i9dsZLy1fnv zXht1aiv%P`j(}#+J#(NZ1DQHG-ln&^bNe26%8~Y>qg^llN)6HbHEicPpu4e?3AfWA zz4JqQCof}Xw{y8$ZDmW0mLjcDk zz+ExY)q_B|2LsqFgVu%NuE@>zx{my9uN@fm;R6E?ueToZfgYFX6%rUc<5%5vC4o~t z!0cYO0U5_$sOOP9PsS+sSuw9S$|l<~c!L7yxYJnJf*ANF2CK&Sz$CoG+L%kd+EmU= zx9OkJtYa`}1CP`juLRB%H=RMS;n)Zq15`Ei8+F z+{BT;4^2W=7;w>VTn2Nl4(n`?pDHPlj3BjOlG)`xZ25Z3t4U@TWl8jXydyJTKmh&< zg8pH?aOcM#rWNF`Q{b=L=dZW!Z$J_7L@>Y*4$UwLFb)bZEeJ6G1fR4K479tBfKNIF z?Ljj<)&spLf_wyn{H_Q2n*;?Sp&6llLE-B`k-rx{dE38SFtnS|zgn9@-Q+81hG(AB z)Y&ix*3d+7*koi_hpf*KCjPa0;M)ScF=yL%K|mA3aN4B6n(G)IHDT}C!iST>hp!Wx z9F6$6j`{GuxJITQTUNw+0mdRlq`h}I+Iey~e-exG1hgO$uka-9MkI_#D2hZiiqtep z2ql8NFiK}qH;Mq^{@4YDQmaNYnMSh&e+!>n{NU@L5yKlCBUBi3mD#j=Bu1nkCruhx zf-+XhG*(7OjmIliu0K{`BUXtr?vha471cOp(>RsjI1!0xRXBXIP=s+KPE#oUu4?>1 zb=(bbd`e{eeK<5@Bi?{A0S?VDL>fTDWbwv@3118#N=j3qJp%~yD)?=Ayz@w+3)}!w zrL!-@AW=#3MH)ayZIeQi8ND`=zA}I)$-)bhdo{w+lZjR+QmO$dxu!&vrYXbRDX>B; zoZ|kJQjL^yHlj44Zw;U}%Crulv@X>&xck!^yw3olOdl0WA6HGEFioEdPM;}EpX*Ow z*hpWZ%z)cHE2l@?_;qH{D1#28K+1jm&Ux=5?`5gElvuP4uXZ zO!df2E&8l&(=6p$qx5dy^V}YKfJ{fPNQ;pyd2V+Uu%}YF(a}aUkNckNML-cO^+^p!yxgnmr6`XRK#p|({w9LTMv+Tr*?9u?jmwB&q zuik!{0e+bt`Eu;sORl#sJpg&`^m*Cm@?h+@^JL?hq#g2fHFAr~^VGmGR*891H7^x! z=UdX}(_81iLChlxJ212`PfRKg9$4 z3?Q}Q8nfcMkm81-;--P(md)Zes*(=jk}kE9ZnKi!kdpqQbiJaIH=89RRHbkO=u!;o z_%{a7tW^}%Cj-cg{#@{H7(hq&8$jft6_mvl)PohYTNU)wm5kJ8LyZ+w1fLeKQ2`v7 zaA?b;J!p%yfeZ)f5g0} z>tv{5xAtV1F+4dEZoWD_836{eO-0&~>cEWyrsOHOaj-|*geONn(KbVkW~LJ&9oS|P zV?P@Qsd@0^$lpfWBtuap36%iPbiyRySyh z7e0cvx#+x3mz_}>+x%SM-P&T0K5QGBF}K68sn!W|fHg+^CbXp=H4$kX(0_rp^tsSs zU8_UVHlIz?;{2~bTM)(p@hvuy_!9y3P4|U91hu)v^Zr3-%eK;qL+9J}83)A%N(A_i zrm8&9FkQ~CM3cjfgM4tDw4q@PJ#LbW^!W~-;FVYw`K0sL8vc6Y;E_oQ{7o8Zgifyh zp`oYLBPJaIZ2?{At=u^lT`rb-d;Mk4tDTfAkNInOj2HSUS{5lzq^5?@r}VX$t{x8E zPSujW(BBcfnwuz}rgN>azk5HlWi=1doTgXg(>LUBAq!y~I1Y5b=|7tLNGx5#Z-9IZ zud+ZByg)k*BAcjjD6DQycLfh%l&jko>8n)8R1bh=`Ou5a#9$eOlqu@-g=-~C?ipqM z?6>a7Ub`_7)?GK#IJ~p5RtCV!1WU*avy=4VfzriorZYzbcmpZ%@g(iu@w{ce(u+y9 zAmI?yHiD~PSv3e)7LpVhK|os&i`VThh7T7(U+pmPxZyq?Mi>VIEmZ^j1C-8#9O%KxNm^dH6X`66p3-S0pzZWLo zUA(^TJ13k2TaB6&-dz7xXv^WZjwhVC0lp>4+eP@;2 zUE^Sf_ypnB2!hhJEJIvCR{BfCHHtSPA<=SsY==(Dl=@?DMG&yJCAPbaUh)myi~ z2;w7bW@Q0pRm=~mvdb#w#LI+Ecd0wEpYfYIwkC9cTphjefye_*<_oVAz|Z+sEbz?& zq(<}~t~HRTV>t#uN6>Si%)D=~J@qBV*SSr~?`*#hY%QMPZ(VOO+xZZQUos`FxX}@^ zvy!M-GIQB?ElL1Dmlk2%c&BWmUwYbVAW?+#37x|suO3O%wry#H#Svxt0BB|Ywun~Q z9TINX>6UAyE7O%mAFo(#^tN7mkPSBuT9?-~WJ^iQ7q@x5%C^qR7HfCidYW5SwS9@8 zOkf0_faqwek3+(5iExX1+YPBUVIqBqcTOoLIL`4%0+!Q0X`zkr|tI(D+z4iv+ zn?oA53=9q$aN6b++xq|q#btY48IWEbQ4cf5W2l3X6UjqkM;8r;`OI6~6^^EG+NKkY z354`11l?Z1$pzx%*6ZW}{gNE%H*)g-9&IxoZXEoGwyA=4HxNnN9J}NgZ0%es;8qQ% zZ5q4P1-dmr+?smb;Ko54xqF9zdzT6#Ino>G-Vbpf>~+t{blpvkjH`G|>?TJb2-@cU zW>$w9(&^AFJC)7S1g9p#hP4LiykjasHj+Hg9X@DEib?+12PNYHJ zlm*_@ecrU|-t-hc2-@a#A0`t-a)hnGhojGj6G_{ICr5a9X`8#rkv`wk>%L+Xe&XMw zZ7K%?Sg>+iu|6Jg(l&9XQx^>QON@i-m#$KTR>PAcm{`{9 zKytwVdy~*S3P3{r)6b|$_!`C$_%Q9WKfvi(ls2)nwz0LdcW`uac5!uc_kcH>eSH0X zw%MEoC@HU~tg3!hQ(ITx(Ad;`&}=?vHvcPB3xV`MoYOT2vO?V*eA|Dc6%A{c?1 z{8Lu}@2HxrjW~YFyH6z`I6Xzy;+spgTnB?p|I9(A4L@9i&%?CcB^h6-mh|;kYZ~W& z_qh`m#HyU%@H*WTftvg^PS3Y_caDl&w%$t0RU~SXtg*L7Xf?~rJ@qd0g>N}M5eyt! z8Ds732-Wh=RqOKCswMm0%aoV9s7Zz6`AF4rVQ}=zxpU&@n$5pz%ZJqNs+K8*{9mY+%{Nf^xwD=?duGLK7;vo$rRL-k znCctV0;y(JSpE^G=SCE>g=B+b%llnUPr>g*P41pMCx;_*S^|DQr>B-}0ze48vsJWv z?reqD2!D0%{IP1e_8rvZSLe=i8?H71Q%mP@QNN65G5{bbr$HP5L^KWSKf!`OJoks* zY9*!r6sBU+zk}y<#t5MBQKo0;u2t^PgEfU=3J1lLwDRk6lg+Ui*>99LO&2j~F+Tmk z@W(h0{4xFjg!UzS=!pb23H{VWcd=#bBL34q^aDE{K6s3)Hy6H~?a92;7%pFwH$QOj z7(aN7BeRDedo=!*6T-s&^68(OJv`vPe^WmF(@qGuiDICyP6!S{|Bmb-UouD_Rr6OL zQ1VaYFdBALE}+2z!rl428^pL$c{Z^S-#E~-kN%DwbmE0(^*FMaWbQ9deO>sX|`-- z>w)Pyv(-CCt9gFQ4;>5epbn9K@81x&mB96S>MhB8}|$ylRmS+6=;ZzovO z)mh_BS;v#@rcRJ+#)yUf3N!>H=ICcgOuuW+o8nD;htXX~OheuRb}bwdvzmr_fP-la z;J(ukDx342o=akTjEwJI&qXQb{P%kmn5n7I6SVBH0~qEHw}-gNU^?y$5pi7y-@iaSJ7}_od~?U z8&^r12-eM;o(M6h`5%d^{Ns&ZY0mZW@4!J1h-AxdT5>VJV1phI$=s9V9{keyb>EMJ zLCzq5{@5I1`htU*Z7zaouzZD`HheT}oW8i=y$^u7&)WF?0L%kxBW)nxV60(pSL@(& zbMU$0c)vd#nLqdbb2{=vv%n-W9Xa=*RqRtbvR#Jx>vZI`M;||>Bj*pS4HmhmPw7Z* zx2XTAbmWJ?I=MeH9eH4F99SC%*2aH!XZ$x>8&5S3iN(GFp31a_TN@T#&u?udF_+C$ zcv^NnjR4I~?m&ymc9vSpwi3?3wr7XoF2|Xj?FXhaYZj{kmMd!(?;n7n4@~Fyt(F|Y zaD(HJmL(1PlqE>Z5>v|>Tg4Pt3ykk%O(u~)Po&VKKA zHa>%Hkv978ayF2r$UdIn?{_vtME(F*{qO--{eY|fFYbLF80m}KzerH$FYX*9sOuY) z3;&r3>R%g-`r+qhKV<@hV9@?2+GZb~0=Z5oh8P*3Jit>N;3@v9t1a84n3#SZDf7dp zdGL{eE4p4DR`-^iCnI8BCk01>Np)Cs8Doj7qTKj&T7k|&Y}2vP)3VcXf%m1f;zKV@ zOmgV3zMt0iPUh1|NL2NaNKVZ=9~Kwiu2h|r-mNp62AODR#wuK$naz0Vk2RO+p?7{R ztBwi2PI$eeVJ@ew5ba@Zm-55;m%Rvn`bBC2$NWJD_65ke|HFlXAFLCa;X(A5QWnbq ze;+^H=zfa_a%7-ctxqSv`B9>0`TQzkWPtx-C7;jQkU2kOjnQiDICRAB(>md(TaK>8 znAh&r_B7;>u3CD5tB(B3Pj#KzVw8+H1fC)_$HxSTrPqq3{y6v?`LSsYUl|S1M~n<0>N}j_yuWHbOiAJwUmh__b7n{UmCbLlU&};`;cFD^(=jsi%JtC zxl!V|ksIy!8Lp!zP-_`^j2;Wz;E<$D%90|MUv{hGxx+Z_gew#dd+S_XKE|@bf+t)_ zsQ!rKB0bEQ>I7q@SC80Tmn|8YA(m_c&ro2j)=G^Oo};&2dF)Z`SlJ8R_r1V?cPAw& z7%!Q=Kjlp=$rhJ&p6JvC~aDs>Cek4N-Zwy_%(zE@KRFTo7Q+YCi4eOEk%$N#2o!@jlKstY_|$ z;5&*|)BZ0=qlU4N9h^ilevqOZiJuUNCQ;m#yVd@e2|?yVZSH&PgvTpw1pG*7nYYiK zFUjn1$Tf_GVMdQq-JQV;UagG3Q9dnhh}9YXuL2ROMR~mcGZTUfLD{8@AJR@yy1+*U zuE}p~XMm9j!QQ~FXK$^~=CjH0_7bk$+I+U z3duBV;+T!snI5Os6pmx7HVWxPoS#a2-aMcwHw1w@}%Z_VBiD zuzV46WZ)L*{3A8L{^fC2&5HT?Cn0J%>9i+OIs(f=l^O;ir zJkXNAVpzN*-;Zw)$9l3-7|JAFS%21`orFiRP*#PPsgeWUeAlKaz#;^>{0}5N8p)EZk z5j`W|*ngqmg`qw#jhG_c##vK6x@7$A$1nnv?#rBe2|-7!ri%6BIn<&yckqk)ds=d+CtRs2 zBI@v1Zx?w(b8)d4J~Cj6X0J{s&b3{Wnr^CLUW%X5+BEG;U>1|FE}B}l9sU|KwQKf` zW+_bc)qO5=>vyG4^~CKUn?;;=M_}tAPfwF#aal~M!QdkUrHFOHdY#fuy5!Q1YW$sJ z=EKyJjut06Ztbi_V4Ds+eL&KC#?7$f7FlSk{#9KwzHUJ7d_Yk1O^&|F#^F@ z5HLfi<1}nJ1Cb*Gy|$;JKO7m5k^n0S*k4hxZ}+xVmLaVUu)oo3uine1 z0<~|Xx7XC-Tf4SzoiOn1ZK%ULYuhI(h;>4vEPG=areM-1W>ClNXXao5B%Z?9DVof2 z%)r5EcVs~8j0?H5t&+msDN{e=Q*KaDqrY>w7Eb^aJF3nqB9K0&7ucihk_>TS<#V2T z>yk?DDr)GGjl@$-FKa;H>x8|ouu>HkL#S)zHzNZqBBm5~WP8T)YGr{g% z$dQ3JQ1?v<*AW4a!E9$Zo??>8V@d#bW{R?_(_@z0^F6t4rVRN<0Z(Wka%4aSe<@KI z*6WEX=!FKSQk-z7af1JSKG6%r)%GF~JmXr4xkm0yYT`{^pxz`+Nu`Z0M&>n}=(=KF`2PHy@AG^ zSRxFX zx1N65j~p$I5+}Ya<{q}yM#EtdIJh=COuKExQYt*cb6&rahi34ginQqb^J6aI!O z$YBWblmhEt86TLUmv{s-bwW>l0$EtV5K`yX3Gt%GJjLb@g(rtak)H|945I=<#TPsU zZbzxIp@n;%xw7FFM7#wh6lRNW(8Zh>Xq{3lvo)NGSpTyHjcIoAiOjq(Ph=;f($5n^ zjVQzgMzD5NtQr*$r8b^$F~#X&hV)8vI!t6QNg9{vhq>ERn=UJ(-l%EnM1`8Dq1uy8 zPJFZNN0Pr#n{)cuBaiEZ_*iR9%YAs#=sk zJBdf(9Br!z?XPFGJEGf@neBMOn<=VCJjI?xRX;_;ejeYkf!UF+>L58fGQ1s`=m1Q? zpT^6!6w0UeE-bU2?TGjc1_XoLS&_QVDZi4(^mR#|w zJU>2eFH>G5mD9X(I=ZWf&{Z7ZDRxC3DeNK&yONIbN)eI$p006Ba|JW3JWIhXQDBGC zk{T3i%DXsdW$#)AFH0#XREn50JcYzD4|PJcIA1krz~8&N<`2E_Kqm=2vlu%Di*kB~tAUTPvfSqzr)~cJ}h< zy=xw5f48>JR;HcK?t5pw@?CA8#MRdb(Z%O0vexR_vvsn#TpzEGrqvB%boEjl|G2LB zX85~A7`33k7N3{tT>QQSy2afu!049?`rq5GZ=&lZTs=0fzv;TpnyTlz^nJe%B%1~f zO~n@6+P0q(f0)#r1Mf=>iNqx!3nk%2NCTqUuqz5)O6fTxF;ozZA5n}|G-EZ($WR+u zP9w)>gPO#Ab^h;bsF1+7yXZRbO9eR z?_>3Vj|fyCzjV$K@DYHI0DQ#LdMeMg!U^~Yz()W+0`L)lj{tlG;3Jd`fR6xt1mGh8 zA2Fy_Oy0EdH`ZnVKH`x(3HXR6_cfaPw);~$H1Ubaj@4i8PI{ibj}Q2W@w0Sf9R`|a zcq?Y2><^^6@BgbiIZ;t=f?eC&ozDER z+j7^l=FuDPYJ3mI{;TuoUmd*zd<1zsFptiAMQ-R%q>i7;M)IQvHt1I|zPlAMz(=^1 zpIDpo<>sq*sBV=jw54ERV&iR_o{%)yzD)OtN#+7#Z*p zXI0Ld8E&Ox`cA<`)iw8^TjQO+O9y;}ZRn4Gx^(&DE1%eh9p4~O{Qt;DKp+XAM38de z$=O4Y^qu$FLlC}s+U(&I0yStX9-AXjgT~@O+Tec1Bx{5u#9uKE<|`-J_ezDtn`uuh zhk7P!Aurrme6A7!=*PbVkKG{UYLCI9a1?_SRMyP3zv$LXd#5JHz|GH0iNKOP7AO&{ zkH0;qMDQKB`2qcSaPxCov~ETE&`I)rUbI!Te)U(%-psA6y=Xrk{M&$k8`1USvH2tZ z>m``u*gpfVL?3@wegETKcP~uc_@91g0LKkDZsy~-nU~7iY@Th-VbepOsjU4c4l9xM z0!dE@NP2>#CrEk%tV9bZ-z$o?aD!JHv|-eH;36$qZnW;9PBL9cVam7QB5m%!%?lKy wXFr{D+<=1gG5FH-4)AUR-fh6Y4K&GjPq$>RZyd1SU(f2tx6a0T=DQ8ypM`F4v;Y7A From 9138268cb834ec10760b08ac6ef1e5fcdb230a62 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 14 Feb 2023 12:08:15 +0100 Subject: [PATCH 117/383] fix: type assert on vpcId when nil --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 5b14e8078..3a9bb32c4 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -332,7 +332,7 @@ func (d *Distribution) extractVpcIDFromPrevPhases(fMerger *merge.Merger) (string } vpcFromFuryctlConf, ok := kubeFromFuryctlConf["vpcId"].(string) - if !ok { + if !ok && !d.dryRun { return vpcID, errCastingVpcIDToStr } From 46430e0460f552d9118c23bd0701170af17cab57 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 27 Jan 2023 11:34:45 +0100 Subject: [PATCH 118/383] Feat: added export kubeconfig message (#187) * feat: added export kubeconfig message * chore: changed wording of logging message * chore: better logging messages Co-authored-by: Ramiro Algozino * chore: removed from Info phase messages * chore: added also to delete command Info * fix: linting * chore: better logging messages Co-authored-by: Ramiro Algozino Co-authored-by: Ramiro Algozino --- cmd/create/cluster.go | 9 --- cmd/delete/cluster.go | 4 -- .../kfd/v1alpha2/eks/create/distribution.go | 4 +- .../kfd/v1alpha2/eks/create/infrastructure.go | 4 +- .../kfd/v1alpha2/eks/create/kubernetes.go | 4 +- internal/apis/kfd/v1alpha2/eks/creator.go | 67 ++++++++++++++++--- .../kfd/v1alpha2/eks/delete/distribution.go | 8 ++- .../kfd/v1alpha2/eks/delete/infrastructure.go | 8 ++- .../kfd/v1alpha2/eks/delete/kubernetes.go | 8 ++- internal/apis/kfd/v1alpha2/eks/deleter.go | 8 +++ 10 files changed, 93 insertions(+), 31 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 93eb2b613..596d0717a 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -185,7 +185,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while initializing cluster creation: %w", err) } - logrus.Info("Creating cluster...") if err := clusterCreator.Create(flags.SkipPhase); err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) @@ -193,14 +192,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while creating cluster: %w", err) } - if !flags.DryRun && flags.Phase == cluster.OperationPhaseAll { - logrus.Info("Cluster created successfully!") - } - - if flags.Phase != cluster.OperationPhaseAll { - logrus.Infof("Phase %s executed successfully!", flags.Phase) - } - cmdEvent.AddSuccessMessage("cluster creation succeeded") tracker.Track(cmdEvent) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index b895465ac..0d7f7f482 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -158,10 +158,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while deleting cluster: %w", err) } - if !flags.DryRun && flags.Phase == cluster.OperationPhaseAll { - logrus.Info("Cluster deleted successfully!") - } - cmdEvent.AddSuccessMessage("Cluster deleted successfully!") tracker.Track(cmdEvent) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 3a9bb32c4..77af3ddc4 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -118,7 +118,9 @@ func NewDistribution( func (d *Distribution) Exec() error { timestamp := time.Now().Unix() - logrus.Info("Running distribution phase") + logrus.Info("Installing Kubernetes Fury Distribution...") + + logrus.Debug("Create: running distribution phase...") logrus.Info("Checking that the cluster is reachable...") diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 22d19d010..b460a7649 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -91,7 +91,9 @@ func NewInfrastructure( } func (i *Infrastructure) Exec(opts []cluster.OperationPhaseOption) error { - logrus.Info("Running infrastructure phase...") + logrus.Info("Creating infrastructure...") + + logrus.Debug("Create: running infrastructure phase...") timestamp := time.Now().Unix() diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 2736c48c2..a445eb740 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -86,7 +86,9 @@ func NewKubernetes( } func (k *Kubernetes) Exec() error { - logrus.Info("Running kubernetes phase...") + logrus.Info("Creating Kubernetes Fury cluster...") + + logrus.Debug("Create: running kubernetes phase...") timestamp := time.Now().Unix() diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 9499b57ea..eea681007 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path" + "path/filepath" "strings" "github.com/sirupsen/logrus" @@ -134,6 +135,14 @@ func (v *ClusterCreator) Create(skipPhase string) error { return fmt.Errorf("error while executing infrastructure phase: %w", err) } + if v.dryRun { + logrus.Info("Infrastructure created successfully (dry-run mode)") + + return nil + } + + logrus.Info("Infrastructure created successfully") + return nil case cluster.OperationPhaseKubernetes: @@ -144,25 +153,42 @@ func (v *ClusterCreator) Create(skipPhase string) error { return fmt.Errorf("error while executing kubernetes phase: %w", err) } - if !v.dryRun { - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) - } + if v.dryRun { + logrus.Info("Kubernetes cluster created successfully (dry-run mode)") + + return nil + } + + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) + } + + logrus.Info("Kubernetes cluster created successfully") + + err = v.logKubeconfig() + if err != nil { + return fmt.Errorf("error while logging kubeconfig path: %w", err) } return nil case cluster.OperationPhaseDistribution: if err = distro.Exec(); err != nil { - return fmt.Errorf("error while executing distribution phase: %w", err) + return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) } - if !v.dryRun { - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) - } + if v.dryRun { + logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") + + return nil } + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) + } + + logrus.Info("Kubernetes Fury Distribution installed successfully") + return nil case cluster.OperationPhaseAll: @@ -171,6 +197,8 @@ func (v *ClusterCreator) Create(skipPhase string) error { "Sometimes this is not possible, for better results limit the scope with the --phase flag.") } + logrus.Info("Creating cluster...") + if v.furyctlConf.Spec.Infrastructure != nil && (skipPhase == "" || skipPhase == cluster.OperationPhaseDistribution) { if err := infra.Exec(infraOpts); err != nil { @@ -202,6 +230,13 @@ func (v *ClusterCreator) Create(skipPhase string) error { } } + logrus.Info("Kubernetes Fury cluster created successfully") + + err = v.logKubeconfig() + if err != nil { + return fmt.Errorf("error while logging kubeconfig path: %w", err) + } + return nil default: @@ -209,6 +244,20 @@ func (v *ClusterCreator) Create(skipPhase string) error { } } +func (*ClusterCreator) logKubeconfig() error { + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current dir: %w", err) + } + + kubeconfigPath := filepath.Join(currentDir, "kubeconfig") + + logrus.Infof("To connect to the cluster, set the path to your kubeconfig with 'export KUBECONFIG=%s'"+ + " or use the '--kubeconfig %s' flag in following executions", kubeconfigPath, kubeconfigPath) + + return nil +} + func (v *ClusterCreator) storeClusterConfig() error { x, err := os.ReadFile(v.paths.ConfigPath) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index f4ecd8035..78a95a3eb 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -93,11 +93,15 @@ func NewDistribution( } func (d *Distribution) Exec() error { - logrus.Info("Deleting distribution phase...") + logrus.Info("Deleting Kubernetes Fury Distribution...") + + logrus.Debug("Delete: running distribution phase...") err := iox.CheckDirIsEmpty(d.OperationPhase.Path) if err == nil { - logrus.Infof("distribution phase already executed, skipping") + logrus.Info("Kubernetes Fury Distribution already deleted, skipping...") + + logrus.Debug("Distribution phase already executed, skipping...") return nil } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index eacf7a753..7e327727d 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -49,13 +49,17 @@ func NewInfrastructure(dryRun bool, workDir, binPath string, kfdManifest config. } func (i *Infrastructure) Exec() error { - logrus.Info("Deleting infrastructure phase...") + logrus.Info("Deleting infrastructure...") + + logrus.Debug("Delete: running infrastructure phase...") timestamp := time.Now().Unix() err := iox.CheckDirIsEmpty(i.OperationPhase.Path) if err == nil { - logrus.Infof("infrastructure phase already executed, skipping...") + logrus.Info("Infrastructure already deleted, skipping...") + + logrus.Debug("Infrastructure phase already executed, skipping...") return nil } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 972104742..d4c2098b8 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -49,13 +49,17 @@ func NewKubernetes(dryRun bool, workDir, binPath string, kfdManifest config.KFD) } func (k *Kubernetes) Exec() error { - logrus.Info("Deleting kubernetes phase...") + logrus.Info("Deleting Kubernetes Fury cluster...") + + logrus.Debug("Delete: running kubernetes phase...") timestamp := time.Now().Unix() err := iox.CheckDirIsEmpty(k.OperationPhase.Path) if err == nil { - logrus.Infof("kubernetes phase already executed, skipping...") + logrus.Info("Kubernetes Fury cluster already deleted, skipping...") + + logrus.Debug("Kubernetes phase already executed, skipping...") return nil } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index bdc3479d5..53417a785 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -88,6 +88,8 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while deleting infrastructure phase: %w", err) } + logrus.Info("Infrastructure deleted successfully") + return nil case cluster.OperationPhaseKubernetes: @@ -95,6 +97,8 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while deleting kubernetes phase: %w", err) } + logrus.Info("Kubernetes cluster deleted successfully") + return nil case cluster.OperationPhaseDistribution: @@ -102,6 +106,8 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while deleting distribution phase: %w", err) } + logrus.Info("Kubernetes Fury Distribution deleted successfully") + return nil case cluster.OperationPhaseAll: @@ -122,6 +128,8 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while deleting infrastructure phase: %w", err) } + logrus.Info("Kubernetes Fury cluster deleted successfully") + return nil default: From 208695111118e707194654f607f906e080df5cba Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 30 Jan 2023 17:03:02 +0100 Subject: [PATCH 119/383] refactor: reduce create func complexity --- internal/apis/kfd/v1alpha2/eks/creator.go | 57 +++++++++++++---------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index eea681007..ecabb09d0 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -98,31 +98,9 @@ func (v *ClusterCreator) SetProperty(name string, value any) { } func (v *ClusterCreator) Create(skipPhase string) error { - infra, err := create.NewInfrastructure(v.furyctlConf, v.kfdManifest, v.paths, v.dryRun) - if err != nil { - return fmt.Errorf("error while initiating infrastructure phase: %w", err) - } - - kube, err := create.NewKubernetes( - v.furyctlConf, - v.kfdManifest, - infra.OutputsPath, - v.paths, - v.dryRun, - ) - if err != nil { - return fmt.Errorf("error while initiating kubernetes phase: %w", err) - } - - distro, err := create.NewDistribution( - v.paths, - v.furyctlConf, - v.kfdManifest, - infra.OutputsPath, - v.dryRun, - ) + infra, kube, distro, err := v.setupPhases() if err != nil { - return fmt.Errorf("error while initiating distribution phase: %w", err) + return err } infraOpts := []cluster.OperationPhaseOption{ @@ -244,6 +222,37 @@ func (v *ClusterCreator) Create(skipPhase string) error { } } +func (v *ClusterCreator) setupPhases() (*create.Infrastructure, *create.Kubernetes, *create.Distribution, error) { + infra, err := create.NewInfrastructure(v.furyctlConf, v.kfdManifest, v.paths, v.dryRun) + if err != nil { + return nil, nil, nil, fmt.Errorf("error while initiating infrastructure phase: %w", err) + } + + kube, err := create.NewKubernetes( + v.furyctlConf, + v.kfdManifest, + infra.OutputsPath, + v.paths, + v.dryRun, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("error while initiating kubernetes phase: %w", err) + } + + distro, err := create.NewDistribution( + v.paths, + v.furyctlConf, + v.kfdManifest, + infra.OutputsPath, + v.dryRun, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("error while initiating distribution phase: %w", err) + } + + return infra, kube, distro, nil +} + func (*ClusterCreator) logKubeconfig() error { currentDir, err := os.Getwd() if err != nil { From 5b8c898073f9a139a4bed96b29288a70b1cef3bf Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:54:26 +0100 Subject: [PATCH 120/383] Feat: only use sudo if necessary (#194) * feat: added isRoot check * feat: added log messages for root/non-root users * chore: minor improvement on log message * fix: linting issues * feat: now the warn shows up only if needed * feat: added info about killing the VPN connection after create --- cmd/delete/cluster.go | 1 + .../kfd/v1alpha2/eks/create/infrastructure.go | 14 ++- internal/apis/kfd/v1alpha2/eks/creator.go | 90 ++++++++++++------- .../kfd/v1alpha2/eks/delete/infrastructure.go | 47 ++++++++-- internal/apis/kfd/v1alpha2/eks/deleter.go | 9 +- internal/apis/kfd/v1alpha2/eks/init.go | 2 +- internal/cluster/deleter.go | 17 +++- internal/tool/openvpn/runner.go | 18 +++- internal/x/os/root.go | 22 +++++ test/expensive/furyctl_test.go | 14 ++- 10 files changed, 185 insertions(+), 49 deletions(-) create mode 100644 internal/x/os/root.go diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 0d7f7f482..73cc55048 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -115,6 +115,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { clusterDeleter, err := cluster.NewDeleter( res.MinimalConf, res.DistroManifest, + flags.FuryctlPath, flags.Phase, basePath, flags.BinPath, diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index b460a7649..acabd5bff 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -29,6 +29,7 @@ import ( bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" + osx "github.com/sighupio/furyctl/internal/x/os" ) const SErrWrapWithStr = "%w: %s" @@ -149,7 +150,18 @@ func (i *Infrastructure) Exec(opts []cluster.OperationPhaseOption) error { if strings.ToLower(opt.Name) == cluster.OperationPhaseOptionVPNAutoConnect { autoConnect, ok := opt.Value.(bool) if autoConnect && ok { - logrus.Info("Connecting to VPN, you will be asked for your SUDO password...") + connectMsg := "Connecting to VPN" + + isRoot, err := osx.IsRoot() + if err != nil { + return fmt.Errorf("error while checking if user is root: %w", err) + } + + if !isRoot { + connectMsg = fmt.Sprintf("%s, you will be asked for your SUDO password", connectMsg) + } + + logrus.Infof("%s...", connectMsg) if err := i.ovRunner.Connect(clientName); err != nil { return fmt.Errorf("error connecting to VPN: %w", err) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index ecabb09d0..22fa45aea 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -121,6 +121,12 @@ func (v *ClusterCreator) Create(skipPhase string) error { logrus.Info("Infrastructure created successfully") + if v.furyctlConf.Spec.Infrastructure != nil { + if v.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil && v.vpnAutoConnect { + logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") + } + } + return nil case cluster.OperationPhaseKubernetes: @@ -170,56 +176,72 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseAll: - if v.dryRun { - logrus.Info("furcytl will try its best to calculate what would have changed. " + - "Sometimes this is not possible, for better results limit the scope with the --phase flag.") - } + return v.allPhases(skipPhase, infraOpts, infra, kube, distro) - logrus.Info("Creating cluster...") + default: + return ErrUnsupportedPhase + } +} - if v.furyctlConf.Spec.Infrastructure != nil && - (skipPhase == "" || skipPhase == cluster.OperationPhaseDistribution) { - if err := infra.Exec(infraOpts); err != nil { - return fmt.Errorf("error while executing infrastructure phase: %w", err) - } +func (v *ClusterCreator) allPhases( + skipPhase string, + infraOpts []cluster.OperationPhaseOption, + infra *create.Infrastructure, + kube *create.Kubernetes, + distro *create.Distribution, +) error { + if v.dryRun { + logrus.Info("furcytl will try its best to calculate what would have changed. " + + "Sometimes this is not possible, for better results limit the scope with the --phase flag.") + } + + logrus.Info("Creating cluster...") + + if v.furyctlConf.Spec.Infrastructure != nil && + (skipPhase == "" || skipPhase == cluster.OperationPhaseDistribution) { + if err := infra.Exec(infraOpts); err != nil { + return fmt.Errorf("error while executing infrastructure phase: %w", err) } + } - if skipPhase != cluster.OperationPhaseKubernetes { - if err := kube.Exec(); err != nil { - return fmt.Errorf("error while executing kubernetes phase: %w", err) - } + if skipPhase != cluster.OperationPhaseKubernetes { + if err := kube.Exec(); err != nil { + return fmt.Errorf("error while executing kubernetes phase: %w", err) + } - if !v.dryRun { - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) - } + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) } } + } - if skipPhase != cluster.OperationPhaseDistribution { - if err = distro.Exec(); err != nil { - return fmt.Errorf("error while executing distribution phase: %w", err) - } + if skipPhase != cluster.OperationPhaseDistribution { + if err := distro.Exec(); err != nil { + return fmt.Errorf("error while executing distribution phase: %w", err) + } - if !v.dryRun { - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) - } + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) } } + } - logrus.Info("Kubernetes Fury cluster created successfully") + logrus.Info("Kubernetes Fury cluster created successfully") - err = v.logKubeconfig() - if err != nil { - return fmt.Errorf("error while logging kubeconfig path: %w", err) + if v.furyctlConf.Spec.Infrastructure != nil { + if v.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") } + } - return nil - - default: - return ErrUnsupportedPhase + err := v.logKubeconfig() + if err != nil { + return fmt.Errorf("error while logging kubeconfig path: %w", err) } + + return nil } func (v *ClusterCreator) setupPhases() (*create.Infrastructure, *create.Kubernetes, *create.Distribution, error) { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index 7e327727d..fcffd7c7c 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -12,19 +12,28 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" + osx "github.com/sighupio/furyctl/internal/x/os" ) type Infrastructure struct { *cluster.OperationPhase - tfRunner *terraform.Runner - dryRun bool + furyctlConf schema.EksclusterKfdV1Alpha2 + tfRunner *terraform.Runner + dryRun bool } -func NewInfrastructure(dryRun bool, workDir, binPath string, kfdManifest config.KFD) (*Infrastructure, error) { +func NewInfrastructure( + furyctlConf schema.EksclusterKfdV1Alpha2, + dryRun bool, + workDir, + binPath string, + kfdManifest config.KFD, +) (*Infrastructure, error) { infraDir := path.Join(workDir, cluster.OperationPhaseInfrastructure) phase, err := cluster.NewOperationPhase(infraDir, kfdManifest.Tools, binPath) @@ -34,6 +43,7 @@ func NewInfrastructure(dryRun bool, workDir, binPath string, kfdManifest config. return &Infrastructure{ OperationPhase: phase, + furyctlConf: furyctlConf, tfRunner: terraform.NewRunner( execx.NewStdExecutor(), terraform.Paths{ @@ -77,8 +87,35 @@ func (i *Infrastructure) Exec() error { return fmt.Errorf("error while deleting infrastructure: %w", err) } - logrus.Warnf("Please, remember to kill the OpenVPN process if" + - " you have chosen to create it in the infrastructure phase") + if i.isVpnConfigured() { + killMsg := "killall openvpn" + + isRoot, err := osx.IsRoot() + if err != nil { + return fmt.Errorf("error while checking if user is root: %w", err) + } + + if !isRoot { + killMsg = fmt.Sprintf("sudo %s", killMsg) + } + + logrus.Warnf("Please, remember to kill the OpenVPN process, "+ + "you can do it with the following command: '%s'", killMsg) + } return nil } + +func (i *Infrastructure) isVpnConfigured() bool { + vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn + if vpn == nil { + return false + } + + instances := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances + if instances == nil { + return true + } + + return *instances > 0 +} diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 53417a785..8f4729d8f 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -11,12 +11,14 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" del "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/delete" "github.com/sighupio/furyctl/internal/cluster" ) type ClusterDeleter struct { kfdManifest config.KFD + furyctlConf schema.EksclusterKfdV1Alpha2 phase string workDir string binPath string @@ -39,6 +41,11 @@ func (d *ClusterDeleter) SetProperty(name string, value any) { d.kfdManifest = kfdManifest } + case cluster.DeleterPropertyFuryctlConf: + if s, ok := value.(schema.EksclusterKfdV1Alpha2); ok { + d.furyctlConf = s + } + case cluster.DeleterPropertyPhase: if s, ok := value.(string); ok { d.phase = s @@ -77,7 +84,7 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while creating kubernetes phase: %w", err) } - infra, err := del.NewInfrastructure(d.dryRun, d.workDir, d.binPath, d.kfdManifest) + infra, err := del.NewInfrastructure(d.furyctlConf, d.dryRun, d.workDir, d.binPath, d.kfdManifest) if err != nil { return fmt.Errorf("error while creating infrastructure phase: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/init.go b/internal/apis/kfd/v1alpha2/eks/init.go index e1654690e..e3daf14f4 100644 --- a/internal/apis/kfd/v1alpha2/eks/init.go +++ b/internal/apis/kfd/v1alpha2/eks/init.go @@ -20,6 +20,6 @@ func init() { cluster.RegisterDeleterFactory( "kfd.sighup.io/v1alpha2", "EKSCluster", - cluster.NewDeleterFactory[*ClusterDeleter](&ClusterDeleter{}), + cluster.NewDeleterFactory[*ClusterDeleter, schema.EksclusterKfdV1Alpha2](&ClusterDeleter{}), ) } diff --git a/internal/cluster/deleter.go b/internal/cluster/deleter.go index fd62f9d05..85fda052e 100644 --- a/internal/cluster/deleter.go +++ b/internal/cluster/deleter.go @@ -9,9 +9,11 @@ import ( "strings" "github.com/sighupio/fury-distribution/pkg/config" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) const ( + DeleterPropertyFuryctlConf = "furyctlconf" DeleterPropertyPhase = "phase" DeleterPropertyWorkDir = "workdir" DeleterPropertyKfdManifest = "kfdmanifest" @@ -23,7 +25,7 @@ const ( var delFactories = make(map[string]map[string]DeleterFactory) //nolint:gochecknoglobals, lll // This patterns requires factories // as global to work with init function. -type DeleterFactory func(props []DeleterProperty) (Deleter, error) +type DeleterFactory func(configPath string, props []DeleterProperty) (Deleter, error) type DeleterProperty struct { Name string @@ -39,6 +41,7 @@ type Deleter interface { func NewDeleter( minimalConf config.Furyctl, kfdManifest config.KFD, + configPath, phase, workDir, binPath, @@ -49,7 +52,7 @@ func NewDeleter( lcResourceType := strings.ToLower(minimalConf.Kind) if factoryFn, ok := delFactories[lcAPIVersion][lcResourceType]; ok { - return factoryFn([]DeleterProperty{ + return factoryFn(configPath, []DeleterProperty{ { Name: DeleterPropertyKfdManifest, Value: kfdManifest, @@ -91,8 +94,14 @@ func RegisterDeleterFactory(apiVersion, kind string, factory DeleterFactory) { delFactories[lcAPIVersion][lcKind] = factory } -func NewDeleterFactory[T Deleter](dd T) DeleterFactory { - return func(props []DeleterProperty) (Deleter, error) { +func NewDeleterFactory[T Deleter, S any](dd T) DeleterFactory { + return func(configPath string, props []DeleterProperty) (Deleter, error) { + furyctlConf, err := yamlx.FromFileV3[S](configPath) + if err != nil { + return nil, err + } + + dd.SetProperty(DeleterPropertyFuryctlConf, furyctlConf) dd.SetProperties(props) return dd, nil diff --git a/internal/tool/openvpn/runner.go b/internal/tool/openvpn/runner.go index 13081c0f2..bf3741bf3 100644 --- a/internal/tool/openvpn/runner.go +++ b/internal/tool/openvpn/runner.go @@ -8,6 +8,7 @@ import ( "fmt" execx "github.com/sighupio/furyctl/internal/x/exec" + osx "github.com/sighupio/furyctl/internal/x/os" ) type Paths struct { @@ -32,8 +33,21 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Connect(name string) error { - err := execx.NewCmd("sudo", execx.CmdOptions{ - Args: []string{r.paths.Openvpn, "--config", fmt.Sprintf("%s.ovpn", name), "--daemon"}, + path := "sudo" + args := []string{r.paths.Openvpn, "--config", fmt.Sprintf("%s.ovpn", name), "--daemon"} + + userIsRoot, err := osx.IsRoot() + if err != nil { + return fmt.Errorf("error while checking if user is root: %w", err) + } + + if userIsRoot { + path = r.paths.Openvpn + args = args[1:] + } + + err = execx.NewCmd(path, execx.CmdOptions{ + Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, }).Run() diff --git a/internal/x/os/root.go b/internal/x/os/root.go new file mode 100644 index 000000000..7f77047d0 --- /dev/null +++ b/internal/x/os/root.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osx + +import ( + "errors" + "fmt" + "os/user" +) + +var ErrCannotGetCurrentUser = errors.New("cannot get current user") + +func IsRoot() (bool, error) { + u, err := user.Current() + if err != nil { + return false, fmt.Errorf("%w: %v", ErrCannotGetCurrentUser, err) + } + + return u.Uid == "0", nil +} diff --git a/test/expensive/furyctl_test.go b/test/expensive/furyctl_test.go index 74ad59ac8..e67689cfd 100644 --- a/test/expensive/furyctl_test.go +++ b/test/expensive/furyctl_test.go @@ -19,6 +19,7 @@ import ( "github.com/onsi/gomega/gexec" "github.com/sighupio/furyctl/internal/cluster" + osx "github.com/sighupio/furyctl/internal/x/os" ) func TestExpensive(t *testing.T) { @@ -118,7 +119,18 @@ var ( } KillOpenVPN = func() (*gexec.Session, error) { - cmd := exec.Command("sudo", "pkill", "openvpn") + var cmd *exec.Cmd + + isRoot, err := osx.IsRoot() + if err != nil { + return nil, err + } + + if isRoot { + cmd = exec.Command("pkill", "openvpn") + } else { + cmd = exec.Command("sudo", "pkill", "openvpn") + } return gexec.Start(cmd, GinkgoWriter, GinkgoWriter) } From 314bf9b39f5f6de128abff38dd058a5f65ec6881 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 3 Feb 2023 11:05:46 +0100 Subject: [PATCH 121/383] Feat: added json logs to external tools (#193) * feat: added json logs to external tools * feat: addressing PR review issues * fix: add newline at end of buffer --- internal/x/bytes/transform.go | 75 ++++++++ internal/x/bytes/transform_test.go | 207 +++++++++++++++++++++ internal/x/exec/cmd.go | 37 +++- internal/x/io/multiwritertransform.go | 54 ++++++ internal/x/io/multiwritertransform_test.go | 78 ++++++++ internal/x/logrus/config.go | 7 + 6 files changed, 448 insertions(+), 10 deletions(-) create mode 100644 internal/x/bytes/transform.go create mode 100644 internal/x/bytes/transform_test.go create mode 100644 internal/x/io/multiwritertransform.go create mode 100644 internal/x/io/multiwritertransform_test.go diff --git a/internal/x/bytes/transform.go b/internal/x/bytes/transform.go new file mode 100644 index 000000000..3be56ef7a --- /dev/null +++ b/internal/x/bytes/transform.go @@ -0,0 +1,75 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bytesx + +import ( + "encoding/json" + "errors" + "fmt" + "regexp" + "time" + + logrusx "github.com/sighupio/furyctl/internal/x/logrus" +) + +type TransformFunc func([]byte) ([]byte, error) + +// This constant was taken from the following public repository: +// Name: stripansi +// URL: https://github.com/acarl005/stripansi +// Commit: 5a71ef0e047df0427e87a79f27009029921f1f9b +// Author: https://github.com/acarl005 +// License: MIT License. + +const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" //nolint:lll // Cannot split regex in multiple lines. + +var ( + ErrJSONTransform = errors.New("error while transform to json") + reg = regexp.MustCompile(ansi) +) + +func StripColor(p []byte) ([]byte, error) { + s := string(p) + + strippedS := reg.ReplaceAllString(s, "") + + return []byte(strippedS), nil +} + +func ToJSONLogFormat(level, action string) TransformFunc { + timestamp := time.Now().Format(time.RFC3339) + + return func(p []byte) ([]byte, error) { + var a *string + + msg := string(p) + + if action != "" { + a = &action + } + + lf := logrusx.LogFormat{ + Level: level, + Action: a, + Msg: msg, + Time: timestamp, + } + + out, err := json.Marshal(lf) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrJSONTransform, err) + } + + return out, nil + } +} + +func AppendNewLine(p []byte) ([]byte, error) { + return append(p, '\n'), nil +} + +func Identity(b []byte) ([]byte, error) { + return b, nil +} diff --git a/internal/x/bytes/transform_test.go b/internal/x/bytes/transform_test.go new file mode 100644 index 000000000..2b72f86e8 --- /dev/null +++ b/internal/x/bytes/transform_test.go @@ -0,0 +1,207 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bytesx_test + +import ( + "strings" + "testing" + + bytesx "github.com/sighupio/furyctl/internal/x/bytes" +) + +func TestStripColor(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + input string + wantStr string + wantErr bool + }{ + { + "empty string", + "", + "", + false, + }, + { + "no color", + "test", + "test", + false, + }, + { + "color", + "\x1b[31mtest\x1b[0m", + "test", + false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + input := []byte(tc.input) + + gotStr, err := bytesx.StripColor(input) + if err != nil && !tc.wantErr { + t.Fatalf("expected to not get an error: %v", err) + } + + if err == nil && tc.wantErr { + t.Fatalf("expected to get an error") + } + + if string(gotStr) != tc.wantStr { + t.Errorf("want = %s, got = %s", tc.wantStr, gotStr) + } + }) + } +} + +func TestToJSONLogFormat(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + setup func() (string, string, string) + wantStr string + wantErr bool + }{ + { + "empty string", + func() (string, string, string) { + return "", "debug", "test" + }, + "\"level\":\"debug\",\"action\":\"test\",\"msg\":\"\"", + false, + }, + { + "nil action", + func() (string, string, string) { + return "", "debug", "" + }, + "\"level\":\"debug\",\"msg\":\"\"", + false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + input, level, action := tc.setup() + + gotStr, err := bytesx.ToJSONLogFormat(level, action)([]byte(input)) + if err != nil && !tc.wantErr { + t.Fatalf("expected to not get an error: %v", err) + } + + if err == nil && tc.wantErr { + t.Fatalf("expected to get an error") + } + + if !strings.Contains(string(gotStr), tc.wantStr) { + t.Errorf("want = %s, got = %s", tc.wantStr, gotStr) + } + }) + } +} + +func TestAppendNewLine(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + input string + wantStr string + wantErr bool + }{ + { + "empty string", + "", + "\n", + false, + }, + { + "simple string", + "test", + "test\n", + false, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + input := []byte(tc.input) + + gotStr, err := bytesx.AppendNewLine(input) + if err != nil && !tc.wantErr { + t.Fatalf("expected to not get an error: %v", err) + } + + if err == nil && tc.wantErr { + t.Fatalf("expected to get an error") + } + + if string(gotStr) != tc.wantStr { + t.Errorf("want = %s, got = %s", tc.wantStr, gotStr) + } + }) + } +} + +func TestIdentity(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + input string + wantStr string + wantErr bool + }{ + { + "empty string", + "", + "", + false, + }, + { + "simple string", + "test", + "test", + false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + input := []byte(tc.input) + + gotStr, err := bytesx.Identity(input) + if err != nil && !tc.wantErr { + t.Fatalf("expected to not get an error: %v", err) + } + + if err == nil && tc.wantErr { + t.Fatalf("expected to get an error") + } + + if string(gotStr) != tc.wantStr { + t.Errorf("want = %s, got = %s", tc.wantStr, gotStr) + } + }) + } +} diff --git a/internal/x/exec/cmd.go b/internal/x/exec/cmd.go index 44a07eee7..c2a6f1c2c 100644 --- a/internal/x/exec/cmd.go +++ b/internal/x/exec/cmd.go @@ -14,6 +14,9 @@ import ( "os/exec" "strings" "time" + + bytesx "github.com/sighupio/furyctl/internal/x/bytes" + iox "github.com/sighupio/furyctl/internal/x/io" ) var ( @@ -32,12 +35,26 @@ func NewCmd(name string, opts CmdOptions) *Cmd { outLog := bytes.NewBufferString("") errLog := bytes.NewBufferString("") - outWriters := []io.Writer{outLog} - errWriters := []io.Writer{errLog} + outWriters := []iox.WriterTransform{{W: outLog}} + errWriters := []iox.WriterTransform{{W: errLog}} if LogFile != nil { - outWriters = append(outWriters, LogFile) - errWriters = append(errWriters, LogFile) + cmd := strings.Split(name, "/") + cmdArgs := strings.Join(opts.Args, " ") + + action := cmd[len(cmd)-1] + " " + cmdArgs + + stripColor := iox.WriterTransform{ + W: LogFile, + Transforms: []bytesx.TransformFunc{ + bytesx.StripColor, + bytesx.ToJSONLogFormat("debug", action), + bytesx.AppendNewLine, + }, + } + + outWriters = append(outWriters, stripColor) + errWriters = append(errWriters, stripColor) } if opts.Executor == nil { @@ -45,21 +62,21 @@ func NewCmd(name string, opts CmdOptions) *Cmd { } if opts.Out != nil { - outWriters = append(outWriters, opts.Out) + outWriters = append(outWriters, iox.WriterTransform{W: opts.Out}) } if opts.Err != nil { - errWriters = append(errWriters, opts.Err) + errWriters = append(errWriters, iox.WriterTransform{W: opts.Err}) } if Debug || LogFile == nil { - outWriters = append(outWriters, os.Stdout) - errWriters = append(errWriters, os.Stderr) + outWriters = append(outWriters, iox.WriterTransform{W: os.Stdout}) + errWriters = append(errWriters, iox.WriterTransform{W: os.Stderr}) } coreCmd := opts.Executor.Command(name, opts.Args...) - coreCmd.Stdout = io.MultiWriter(outWriters...) - coreCmd.Stderr = io.MultiWriter(errWriters...) + coreCmd.Stdout = iox.MultiWriterTransform(outWriters...) + coreCmd.Stderr = iox.MultiWriterTransform(errWriters...) coreCmd.Dir = opts.WorkDir return &Cmd{ diff --git a/internal/x/io/multiwritertransform.go b/internal/x/io/multiwritertransform.go new file mode 100644 index 000000000..dc3746de9 --- /dev/null +++ b/internal/x/io/multiwritertransform.go @@ -0,0 +1,54 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package iox + +import ( + "errors" + "fmt" + "io" + + bytesx "github.com/sighupio/furyctl/internal/x/bytes" +) + +var ErrWriterTransform = errors.New("writer transform error") + +type WriterTransform struct { + W io.Writer + Transforms []bytesx.TransformFunc +} + +type multiWriterTransform struct { + writers []WriterTransform +} + +func (m *multiWriterTransform) Write(p []byte) (int, error) { + var err error + + for _, w := range m.writers { + s := p + + for _, transform := range w.Transforms { + s, err = transform(s) + if err != nil { + return 0, fmt.Errorf("%w: %v", ErrWriterTransform, err) + } + } + + n, err := w.W.Write(s) + if err != nil { + return n, fmt.Errorf("%w: %v", ErrWriterTransform, err) + } + + if n != len(s) { + return n, io.ErrShortWrite + } + } + + return len(p), nil +} + +func MultiWriterTransform(writers ...WriterTransform) io.Writer { + return &multiWriterTransform{writers} +} diff --git a/internal/x/io/multiwritertransform_test.go b/internal/x/io/multiwritertransform_test.go new file mode 100644 index 000000000..875f3687a --- /dev/null +++ b/internal/x/io/multiwritertransform_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package iox_test + +import ( + "bytes" + "io" + "testing" + + bytesx "github.com/sighupio/furyctl/internal/x/bytes" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +func setup(t *testing.T) (*bytes.Buffer, io.Writer) { + t.Helper() + + stringBuffer := bytes.NewBufferString("") + + multiWriter := iox.MultiWriterTransform(iox.WriterTransform{ + W: stringBuffer, + Transforms: []bytesx.TransformFunc{bytesx.Identity}, + }) + + return stringBuffer, multiWriter +} + +func TestWrite(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + input string + wantStr string + wantErr bool + }{ + { + "empty string", + "", + "", + false, + }, + { + "simple string", + "test", + "test", + false, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + input := tc.input + + buf, multiWriter := setup(t) + + _, err := multiWriter.Write([]byte(input)) + if err != nil && !tc.wantErr { + t.Fatalf("expected to not get an error: %v", err) + } + + if err == nil && tc.wantErr { + t.Fatalf("expected to get an error") + } + + gotStr := buf.String() + + if gotStr != tc.wantStr { + t.Errorf("want = %s, got = %s", tc.wantStr, gotStr) + } + }) + } +} diff --git a/internal/x/logrus/config.go b/internal/x/logrus/config.go index d767b666c..7b604ae8b 100644 --- a/internal/x/logrus/config.go +++ b/internal/x/logrus/config.go @@ -12,6 +12,13 @@ import ( "github.com/sirupsen/logrus" ) +type LogFormat struct { + Level string `json:"level"` + Action *string `json:"action,omitempty"` + Msg string `json:"msg"` + Time string `json:"time"` +} + type formatterHook struct { Writer io.Writer LogLevels []logrus.Level From 487ca3c16e562a4c3fd4e7277966e65581f8c67f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 3 Feb 2023 11:12:30 +0100 Subject: [PATCH 122/383] feat: removed validation tool logs when in dry-run (#195) --- cmd/create/cluster.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 596d0717a..e627c3dae 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -98,7 +98,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { distrodl := distribution.NewDownloader(client) // Init packages. - execx.Debug = flags.Debug || flags.DryRun execx.NoTTY = flags.NoTTY // Download the distribution. @@ -169,6 +168,9 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { Kubeconfig: flags.Kubeconfig, } + // Set debug mode. + execx.Debug = flags.Debug || flags.DryRun + // Create the cluster. clusterCreator, err := cluster.NewCreator( res.MinimalConf, From 27f23cca903eadaa1a2c261ed7ebb7f42b617803 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:53:11 +0100 Subject: [PATCH 123/383] Feat: add VPC connectivity check in Kubernetes phase (#198) * feat: testing connectivity check with infra * fix: moved connect check after tf plan * feat: support to infrastructure vpc dns check * feat: also check on delete WIP * feat: added vpc check also to delete * fix: check needed before tf plan * fix: nitpick upper VPC * fix: unhandled nil pointer * fix: added skip to delete phase infra if not present * fix: post review change requests Co-authored-by: Claudio Beatrice * fix: post review nitpicks Co-authored-by: Ramiro Algozino * chore: apply suggestions from code review Co-authored-by: Ramiro Algozino * fix: linting * fix: wrong log message error on kubeconfig gen from output --------- Co-authored-by: Claudio Beatrice Co-authored-by: Ramiro Algozino --- go.mod | 3 + go.sum | 15 +++ .../kfd/v1alpha2/eks/create/kubernetes.go | 94 ++++++++++++++- .../kfd/v1alpha2/eks/delete/infrastructure.go | 4 + .../kfd/v1alpha2/eks/delete/kubernetes.go | 107 +++++++++++++++++- internal/apis/kfd/v1alpha2/eks/deleter.go | 11 +- internal/tool/awscli/runner.go | 19 ++++ internal/x/net/dns.go | 35 ++++++ internal/x/net/dns_test.go | 55 +++++++++ internal/x/net/ip.go | 40 +++++++ internal/x/net/ip_test.go | 82 ++++++++++++++ 11 files changed, 454 insertions(+), 11 deletions(-) create mode 100644 internal/x/net/dns.go create mode 100644 internal/x/net/dns_test.go create mode 100644 internal/x/net/ip.go create mode 100644 internal/x/net/ip_test.go diff --git a/go.mod b/go.mod index e5ad0f5a8..77fa3a393 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/go-playground/validator/v10 v10.11.1 github.com/google/go-cmp v0.5.9 github.com/hashicorp/terraform-json v0.14.0 + github.com/miekg/dns v1.1.50 github.com/onsi/ginkgo/v2 v2.6.1 github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 @@ -80,10 +81,12 @@ require ( github.com/zclconf/go-cty v1.12.1 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.4.0 // indirect + golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.4.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect + golang.org/x/tools v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.105.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index a7e1ec90e..4b4c4022d 100644 --- a/go.sum +++ b/go.sum @@ -246,6 +246,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -328,6 +330,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= @@ -389,7 +392,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -423,6 +429,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -450,7 +458,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -485,8 +495,10 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -566,7 +578,10 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index a445eb740..72551ea3d 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io/fs" + "net" "os" "path" "strconv" @@ -23,24 +24,35 @@ import ( "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" + "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" kubex "github.com/sighupio/furyctl/internal/x/kube" + netx "github.com/sighupio/furyctl/internal/x/net" ) var ( - errKubeconfigFromLogs = errors.New("can't get kubeconfig from logs") - errPvtSubnetNotFound = errors.New("private_subnets not found in infra output") + errKubeconfigFromLogs = errors.New("cannot get kubeconfig file after cluster creation") + errPvtSubnetNotFound = errors.New("private_subnets not found in infrastructure phase's output") errPvtSubnetFromOut = errors.New("cannot read private_subnets from infrastructure's output.json") errVpcCIDRFromOut = errors.New("cannot read vpc_cidr_block from infrastructure's output.json") errVpcCIDRNotFound = errors.New("vpc_cidr_block not found in infra output") - errVpcIDNotFound = errors.New("vpc id not found: you forgot to specify one or the infrastructure phase failed") + errVpcIDNotFound = errors.New("vpcId not found: you forgot to specify one in the configuration file " + + "or the infrastructure phase failed") + errParsingCIDR = errors.New("error parsing CIDR") + errResolvingDNS = errors.New("error resolving DNS record") + errVpcIDNotProvided = errors.New("vpcId not provided") + errCIDRBlockFromVpc = errors.New("error getting CIDR block from VPC") + errKubeAPIUnreachable = errors.New("kubernetes API is not reachable") + errAddingOffsetToIPNet = errors.New("error adding offset to ipnet") ) const ( nodePoolDefaultVolumeSize = 35 + // https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html + awsDNSServerIPOffset = 2 ) type Kubernetes struct { @@ -49,6 +61,7 @@ type Kubernetes struct { kfdManifest config.KFD infraOutputsPath string tfRunner *terraform.Runner + awsRunner *awscli.Runner dryRun bool } @@ -81,17 +94,24 @@ func NewKubernetes( Terraform: phase.TerraformPath, }, ), + awsRunner: awscli.NewRunner( + execx.NewStdExecutor(), + awscli.Paths{ + Awscli: "aws", + WorkDir: phase.Path, + }, + ), dryRun: dryRun, }, nil } func (k *Kubernetes) Exec() error { + timestamp := time.Now().Unix() + logrus.Info("Creating Kubernetes Fury cluster...") logrus.Debug("Create: running kubernetes phase...") - timestamp := time.Now().Unix() - if err := k.CreateFolder(); err != nil { return fmt.Errorf("error creating kubernetes phase folder: %w", err) } @@ -120,6 +140,20 @@ func (k *Kubernetes) Exec() error { return nil } + logrus.Info("Checking connection to the VPC...") + + if err := k.checkVPCConnection(); err != nil { + logrus.Debugf("error checking VPC connection: %v", err) + + if k.furyctlConf.Spec.Infrastructure != nil { + if k.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) + } + } + + return fmt.Errorf("%w please check your VPC configuration and try again", errKubeAPIUnreachable) + } + logrus.Info("Creating cloud resources, this could take a while...") out, err := k.tfRunner.Apply(timestamp) @@ -795,3 +829,53 @@ func (*Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np schema.Sp return nil } + +func (k *Kubernetes) checkVPCConnection() error { + var ( + cidr string + err error + ) + + if k.furyctlConf.Spec.Infrastructure != nil { + cidr = string(k.furyctlConf.Spec.Infrastructure.Vpc.Network.Cidr) + } else { + vpcID := k.furyctlConf.Spec.Kubernetes.VpcId + if vpcID == nil { + return errVpcIDNotProvided + } + + cidr, err = k.awsRunner.Ec2( + "describe-vpcs", + "--vpc-ids", + string(*vpcID), + "--query", + "Vpcs[0].CidrBlock", + "--output", + "text", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errCIDRBlockFromVpc, err) + } + } + + return k.queryAWSDNSServer(cidr) +} + +func (*Kubernetes) queryAWSDNSServer(cidr string) error { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errParsingCIDR, err) + } + + offIPNet, err := netx.AddOffsetToIPNet(ipNet, awsDNSServerIPOffset) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errAddingOffsetToIPNet, err) + } + + err = netx.DNSQuery(offIPNet.IP.String(), "google.com.") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errResolvingDNS, err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index fcffd7c7c..1f6962f0a 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -107,6 +107,10 @@ func (i *Infrastructure) Exec() error { } func (i *Infrastructure) isVpnConfigured() bool { + if i.furyctlConf.Spec.Infrastructure == nil { + return false + } + vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn if vpn == nil { return false diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index d4c2098b8..a0e1014b9 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -5,26 +5,54 @@ package del import ( + "errors" "fmt" + "net" "path" "time" "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema" "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" + netx "github.com/sighupio/furyctl/internal/x/net" +) + +const ( + SErrWrapWithStr = "%w: %s" + // https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html + awsDNSServerIPOffset = 2 +) + +var ( + errParsingCIDR = errors.New("error parsing CIDR") + errResolvingDNS = errors.New("error resolving DNS record") + errVpcIDNotProvided = errors.New("vpc_id not provided") + errCIDRBlockFromVpc = errors.New("error getting CIDR block from VPC") + errKubeAPIUnreachable = errors.New("kubernetes API is not reachable") + errAddingOffsetToIPNet = errors.New("error adding offset to ipnet") ) type Kubernetes struct { *cluster.OperationPhase - tfRunner *terraform.Runner - dryRun bool + furyctlConf schema.EksclusterKfdV1Alpha2 + tfRunner *terraform.Runner + awsRunner *awscli.Runner + dryRun bool } -func NewKubernetes(dryRun bool, workDir, binPath string, kfdManifest config.KFD) (*Kubernetes, error) { +func NewKubernetes( + furyctlConf schema.EksclusterKfdV1Alpha2, + dryRun bool, + workDir, + binPath string, + kfdManifest config.KFD, +) (*Kubernetes, error) { kubeDir := path.Join(workDir, cluster.OperationPhaseKubernetes) phase, err := cluster.NewOperationPhase(kubeDir, kfdManifest.Tools, binPath) @@ -34,6 +62,7 @@ func NewKubernetes(dryRun bool, workDir, binPath string, kfdManifest config.KFD) return &Kubernetes{ OperationPhase: phase, + furyctlConf: furyctlConf, tfRunner: terraform.NewRunner( execx.NewStdExecutor(), terraform.Paths{ @@ -44,6 +73,13 @@ func NewKubernetes(dryRun bool, workDir, binPath string, kfdManifest config.KFD) Terraform: phase.TerraformPath, }, ), + awsRunner: awscli.NewRunner( + execx.NewStdExecutor(), + awscli.Paths{ + Awscli: "aws", + WorkDir: phase.Path, + }, + ), dryRun: dryRun, }, nil } @@ -64,6 +100,20 @@ func (k *Kubernetes) Exec() error { return nil } + logrus.Info("Checking connection to the VPC...") + + if err := k.checkVPCConnection(); err != nil { + logrus.Debugf("error checking VPC connection: %v", err) + + if k.furyctlConf.Spec.Infrastructure != nil { + if k.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) + } + } + + return fmt.Errorf("%w please check your VPC configuration and try again", errKubeAPIUnreachable) + } + if err := k.tfRunner.Init(); err != nil { return fmt.Errorf("error running terraform init: %w", err) } @@ -76,6 +126,8 @@ func (k *Kubernetes) Exec() error { return nil } + logrus.Info("Deleting cloud resources, this could take a while...") + err = k.tfRunner.Destroy() if err != nil { return fmt.Errorf("error while deleting kubernetes phase: %w", err) @@ -83,3 +135,52 @@ func (k *Kubernetes) Exec() error { return nil } + +func (k *Kubernetes) checkVPCConnection() error { + var cidr string + + var err error + + if k.furyctlConf.Spec.Infrastructure != nil { + cidr = string(k.furyctlConf.Spec.Infrastructure.Vpc.Network.Cidr) + } else { + vpcID := k.furyctlConf.Spec.Kubernetes.VpcId + if vpcID == nil { + return errVpcIDNotProvided + } + + cidr, err = k.awsRunner.Ec2( + "describe-vpcs", + "--vpc-ids", + string(*vpcID), + "--query", + "Vpcs[0].CidrBlock", + "--output", + "text", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errCIDRBlockFromVpc, err) + } + } + + return k.queryAWSDNSServer(cidr) +} + +func (*Kubernetes) queryAWSDNSServer(cidr string) error { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errParsingCIDR, err) + } + + offIPNet, err := netx.AddOffsetToIPNet(ipNet, awsDNSServerIPOffset) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errAddingOffsetToIPNet, err) + } + + err = netx.DNSQuery(offIPNet.IP.String(), "google.com.") + if err != nil { + return fmt.Errorf(SErrWrapWithStr, errResolvingDNS, err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 8f4729d8f..4063d5c76 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -79,7 +79,7 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while creating distribution phase: %w", err) } - kube, err := del.NewKubernetes(d.dryRun, d.workDir, d.binPath, d.kfdManifest) + kube, err := del.NewKubernetes(d.furyctlConf, d.dryRun, d.workDir, d.binPath, d.kfdManifest) if err != nil { return fmt.Errorf("error while creating kubernetes phase: %w", err) } @@ -100,6 +100,9 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseKubernetes: + logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + + " (e.g. check VPN connection is active`), otherwise the deletion will fail.") + if err := kube.Exec(); err != nil { return fmt.Errorf("error while deleting kubernetes phase: %w", err) } @@ -131,8 +134,10 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while deleting kubernetes phase: %w", err) } - if err := infra.Exec(); err != nil { - return fmt.Errorf("error while deleting infrastructure phase: %w", err) + if d.furyctlConf.Spec.Infrastructure != nil { + if err := infra.Exec(); err != nil { + return fmt.Errorf("error while deleting infrastructure phase: %w", err) + } } logrus.Info("Kubernetes Fury cluster deleted successfully") diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index 67c75014e..66491e90e 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -31,6 +31,25 @@ func (r *Runner) CmdPath() string { return r.paths.Awscli } +func (r *Runner) Ec2(sub string, params ...string) (string, error) { + args := []string{"ec2", sub} + + if len(params) > 0 { + args = append(args, params...) + } + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error running awscli ec2 %s: %w", sub, err) + } + + return out, nil +} + func (r *Runner) Version() (string, error) { out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ Args: []string{"--version"}, diff --git a/internal/x/net/dns.go b/internal/x/net/dns.go new file mode 100644 index 000000000..e8a9a0c0c --- /dev/null +++ b/internal/x/net/dns.go @@ -0,0 +1,35 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package netx + +import ( + "errors" + "fmt" + + "github.com/miekg/dns" +) + +var ErrDNSQueryFailed = errors.New("DNS query failed") + +func DNSQuery(server, target string) error { + config := dns.ClientConfig{Servers: []string{server}, Port: "53"} + + client := dns.Client{} + + message := dns.Msg{} + + message.SetQuestion(target, dns.TypeA) + + r, _, err := client.Exchange(&message, config.Servers[0]+":"+config.Port) + if err != nil { + return fmt.Errorf("%w: %v", ErrDNSQueryFailed, err) + } + + if r.Rcode != dns.RcodeSuccess { + return fmt.Errorf("%w: %s", ErrDNSQueryFailed, dns.RcodeToString[r.Rcode]) + } + + return nil +} diff --git a/internal/x/net/dns_test.go b/internal/x/net/dns_test.go new file mode 100644 index 000000000..4e64bc750 --- /dev/null +++ b/internal/x/net/dns_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build integration + +package netx_test + +import ( + "testing" + + netx "github.com/sighupio/furyctl/internal/x/net" +) + +func TestDNSQuery(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + server string + target string + wantErr bool + }{ + { + name: "test valid dns query", + server: "8.8.8.8", + target: "google.com.", + wantErr: false, + }, + { + name: "test invalid dns query", + server: "8.8.8.8", + target: "google.com", + wantErr: true, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + err := netx.DNSQuery(tc.server, tc.target) + + if tc.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tc.wantErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} diff --git a/internal/x/net/ip.go b/internal/x/net/ip.go new file mode 100644 index 000000000..9c42590c4 --- /dev/null +++ b/internal/x/net/ip.go @@ -0,0 +1,40 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package netx + +import ( + "fmt" + "math" + "net" + "net/netip" +) + +var ( + ErrInvalidIP = fmt.Errorf("invalid ip") + ErrIPNetIsNil = fmt.Errorf("ipnet is nil") +) + +func AddOffsetToIPNet(ipNet *net.IPNet, offset int) (*net.IPNet, error) { + if ipNet == nil { + return nil, ErrIPNetIsNil + } + + newIP, err := netip.ParseAddr(ipNet.IP.String()) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrInvalidIP, err) + } + + for i := 0; i < int(math.Abs(float64(offset))); i++ { + if offset > 0 { + newIP = newIP.Next() + + continue + } + + newIP = newIP.Prev() + } + + return &net.IPNet{IP: newIP.AsSlice(), Mask: ipNet.Mask}, nil +} diff --git a/internal/x/net/ip_test.go b/internal/x/net/ip_test.go new file mode 100644 index 000000000..25a1cff2c --- /dev/null +++ b/internal/x/net/ip_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package netx_test + +import ( + "errors" + "net" + "testing" + + netx "github.com/sighupio/furyctl/internal/x/net" +) + +func TestAddOffsetToIPNet(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + ipNet *net.IPNet + offset int + want *net.IPNet + wantErr *error + }{ + { + name: "test valid ipnet positive offset", + ipNet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32)}, + offset: 1, + want: &net.IPNet{IP: net.IPv4(192, 168, 0, 1), Mask: net.CIDRMask(24, 32)}, + wantErr: nil, + }, + { + name: "test valid ipnet negative offset", + ipNet: &net.IPNet{IP: net.IPv4(192, 168, 0, 1), Mask: net.CIDRMask(24, 32)}, + offset: -1, + want: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32)}, + wantErr: nil, + }, + { + name: "test nil ipnet", + ipNet: nil, + offset: 1, + want: nil, + wantErr: &netx.ErrIPNetIsNil, + }, + { + name: "test invalid ipnet", + ipNet: &net.IPNet{IP: []byte{0, 168, 0, 0, 1}, Mask: net.CIDRMask(24, 32)}, + offset: 1, + want: nil, + wantErr: &netx.ErrInvalidIP, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got, err := netx.AddOffsetToIPNet(tc.ipNet, tc.offset) + + if tc.wantErr != nil { + if err == nil { + t.Fatalf("expected error %v, got nil", *tc.wantErr) + } + + if !errors.Is(err, *tc.wantErr) { + t.Errorf("expected error %v, got %v", *tc.wantErr, err) + } + + return + } + + if got.String() != tc.want.String() { + t.Errorf("expected %v, got %v", tc.want, got) + } + }) + } +} From 82dcf398f19a8d4b9b1570babc69e16c02fd77d4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:44:26 +0100 Subject: [PATCH 124/383] Chore: moved path validation before distro download (#275) * chore: moved path validation before distro download * chore: remove hardcoded filename Co-authored-by: Ramiro Algozino --------- Co-authored-by: Ramiro Algozino --- cmd/create/config.go | 23 ++++++++++++++++++++++- internal/config/validate.go | 12 ------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/cmd/create/config.go b/cmd/create/config.go index 539e01f5f..3b48500da 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -7,6 +7,8 @@ package create import ( "errors" "fmt" + "os" + "path/filepath" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -22,7 +24,10 @@ import ( netx "github.com/sighupio/furyctl/internal/x/net" ) -var ErrMandatoryFlag = errors.New("flag must be specified") +var ( + ErrMandatoryFlag = errors.New("flag must be specified") + ErrConfigCreationFailed = fmt.Errorf("config creation failed") +) func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { var cmdEvent analytics.Event @@ -90,6 +95,22 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { // Init packages. execx.Debug = debug + // Validate path. + if _, err := os.Stat(furyctlPath); err == nil { + absPath, err := filepath.Abs(furyctlPath) + if err != nil { + return fmt.Errorf("%w: error while getting absolute path %v", ErrConfigCreationFailed, err) + } + + p := filepath.Dir(absPath) + + return fmt.Errorf( + "%w: a configuration file already exists in %s, please remove it and try again", + ErrConfigCreationFailed, + p, + ) + } + // Download the distribution. logrus.Info("Downloading distribution...") res, err := distrodl.DoDownload(distroLocation, minimalConf) diff --git a/internal/config/validate.go b/internal/config/validate.go index 37bb0ea03..de9616986 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -18,8 +18,6 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -var ErrConfigCreationFailed = fmt.Errorf("config creation failed") - func Create( res distribution.DownloadResult, furyctlPath string, @@ -72,16 +70,6 @@ func createNewEmptyConfigFile(path string) (*os.File, error) { return nil, fmt.Errorf("error getting absolute path: %w", err) } - if _, err := os.Stat(absPath); err == nil { - p := filepath.Dir(absPath) - - return nil, fmt.Errorf( - "%w: a furyctl.yaml configuration file already exists in %s, please remove it and try again", - ErrConfigCreationFailed, - p, - ) - } - if err := os.MkdirAll(filepath.Dir(absPath), iox.FullPermAccess); err != nil { return nil, fmt.Errorf("error creating directory: %w", err) } From 335119fe16ab8c2f30afbd493d1858c9f497c79f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:52:24 +0100 Subject: [PATCH 125/383] fix: now --dry-run does not imply --debug for external tools (#274) --- cmd/create/cluster.go | 2 +- cmd/delete/cluster.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index e627c3dae..7f6df9b82 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -169,7 +169,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { } // Set debug mode. - execx.Debug = flags.Debug || flags.DryRun + execx.Debug = flags.Debug // Create the cluster. clusterCreator, err := cluster.NewCreator( diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 73cc55048..c683ede7c 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -86,7 +86,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { executor := execx.NewStdExecutor() distrodl := distribution.NewDownloader(client) - execx.Debug = flags.Debug || flags.DryRun + execx.Debug = flags.Debug execx.NoTTY = flags.NoTTY // Download the distribution. From 32cac0b5aa5b6710576abc5668ce59d500d34207 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:41:28 +0100 Subject: [PATCH 126/383] Feat: check if kubeconfig file exists (#276) --- cmd/create/cluster.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 7f6df9b82..abe88e90d 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -29,6 +29,7 @@ var ( ErrParsingFlag = errors.New("error while parsing flag") ErrDownloadDependenciesFailed = errors.New("dependencies download failed") ErrKubeconfigReq = errors.New("when running distribution phase alone, either the KUBECONFIG environment variable or the --kubeconfig flag should be set") + ErrKubeconfigNotFound = errors.New("kubeconfig file not found") ) type ClusterCmdFlags struct { @@ -71,6 +72,8 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while getting current working directory: %w", err) } + kubeconfigPath := flags.Kubeconfig + // Check if kubeconfig is needed. if flags.Phase == cluster.OperationPhaseDistribution || flags.SkipPhase == cluster.OperationPhaseKubernetes { if flags.Kubeconfig == "" { @@ -80,8 +83,20 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return ErrKubeconfigReq } + kubeconfigPath = kubeconfigFromEnv + logrus.Warnf("Missing --kubeconfig flag, falling back to KUBECONFIG from environment: %s", kubeconfigFromEnv) } + + // Check the kubeconfig file exists. + if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) { + kubeAbsPath, err := filepath.Abs(kubeconfigPath) + if err != nil { + return fmt.Errorf("error while getting absolute path of kubeconfig: %w", err) + } + + return fmt.Errorf("%w in %s", ErrKubeconfigNotFound, kubeAbsPath) + } } if flags.BinPath == "" { From 60a50d71c1177f17464396064225899e050f2bdb Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:18:46 +0100 Subject: [PATCH 127/383] Fix: check that the infra section exists before infra phase (#271) * fix: check that the infra section exists before initialising the infra phase. * chore: reword error message and add path to config file --- internal/apis/kfd/v1alpha2/eks/creator.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 22fa45aea..2454eb565 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -24,7 +24,10 @@ import ( kubex "github.com/sighupio/furyctl/internal/x/kube" ) -var ErrUnsupportedPhase = errors.New("unsupported phase") +var ( + ErrUnsupportedPhase = errors.New("unsupported phase") + ErrInfraNotPresent = errors.New("the configuration file does not contain an infrastructure section") +) type ClusterCreator struct { paths cluster.CreatorPaths @@ -109,6 +112,17 @@ func (v *ClusterCreator) Create(skipPhase string) error { switch v.phase { case cluster.OperationPhaseInfrastructure: + if v.furyctlConf.Spec.Infrastructure == nil { + absPath, err := filepath.Abs(v.paths.ConfigPath) + if err != nil { + logrus.Debugf("error while getting absolute path of %s: %v", v.paths.ConfigPath, err) + + return fmt.Errorf("%w: at %s", ErrInfraNotPresent, v.paths.ConfigPath) + } + + return fmt.Errorf("%w: check at %s", ErrInfraNotPresent, absPath) + } + if err = infra.Exec(infraOpts); err != nil { return fmt.Errorf("error while executing infrastructure phase: %w", err) } From 41735e5d00429c268fe5cf0abf559a1ffe2320dd Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Wed, 22 Feb 2023 16:43:03 +0100 Subject: [PATCH 128/383] fix: added to template logRetentionDays and launchKind (#301) --- configs/provisioners/cluster/eks/main.tf.tpl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index 31d9ed561..9ba8e42d4 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -21,14 +21,16 @@ terraform { module "fury" { source = "{{ .kubernetes.installerPath }}" - cluster_name = var.cluster_name - cluster_version = var.cluster_version - network = var.network - subnetworks = var.subnetworks - dmz_cidr_range = var.dmz_cidr_range - ssh_public_key = var.ssh_public_key - node_pools = var.node_pools - tags = var.tags + cluster_name = var.cluster_name + cluster_version = var.cluster_version + cluster_log_retention_days = var.cluster_log_retention_days + network = var.network + subnetworks = var.subnetworks + dmz_cidr_range = var.dmz_cidr_range + ssh_public_key = var.ssh_public_key + node_pools = var.node_pools + node_pools_launch_kind = var.node_pools_launch_kind + tags = var.tags # Specific AWS variables. # Enables managing auth using these variables From 94d018d641e2e3ed1e868022965aa3b83f2b3118 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:01:04 +0100 Subject: [PATCH 129/383] Feat: added support to AWS_PROFILE (#277) * feat: added support to AWS_PROFILE * fix: apply suggestions from code review Co-authored-by: Claudio Beatrice * fix: e2e tests * fix: typo in tests * chore: apply suggestions from code review Co-authored-by: Ramiro Algozino --------- Co-authored-by: Claudio Beatrice Co-authored-by: Ramiro Algozino --- internal/dependencies/envvars/validator.go | 33 +++++++++++++++++----- test/e2e/furyctl_test.go | 12 ++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/internal/dependencies/envvars/validator.go b/internal/dependencies/envvars/validator.go index 75b26acc2..001c3d40f 100644 --- a/internal/dependencies/envvars/validator.go +++ b/internal/dependencies/envvars/validator.go @@ -8,9 +8,13 @@ import ( "errors" "fmt" "os" + "strings" ) -var ErrMissingEnvVar = errors.New("missing environment variable") +var ( + ErrMissingEnvVars = errors.New("missing environment variables") + ErrMissingRequiredEnvVar = errors.New("missing required environment variable") +) func NewValidator() *Validator { return &Validator{} @@ -30,22 +34,37 @@ func (*Validator) checkEKSCluster() ([]string, []error) { oks := make([]string, 0) errs := make([]error, 0) + var missingAwsVars []string + + if os.Getenv("AWS_DEFAULT_REGION") == "" { + errs = append(errs, fmt.Errorf("%w: AWS_DEFAULT_REGION", ErrMissingRequiredEnvVar)) + } else { + oks = append(oks, "AWS_DEFAULT_REGION") + } + + if os.Getenv("AWS_PROFILE") != "" { + oks = append(oks, "AWS_PROFILE") + + return oks, errs + } + if os.Getenv("AWS_ACCESS_KEY_ID") == "" { - errs = append(errs, fmt.Errorf("AWS_ACCESS_KEY_ID: %w", ErrMissingEnvVar)) + missingAwsVars = append(missingAwsVars, "AWS_ACCESS_KEY_ID") } else { oks = append(oks, "AWS_ACCESS_KEY_ID") } if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { - errs = append(errs, fmt.Errorf("AWS_SECRET_ACCESS_KEY: %w", ErrMissingEnvVar)) + missingAwsVars = append(missingAwsVars, "AWS_SECRET_ACCESS_KEY") } else { oks = append(oks, "AWS_SECRET_ACCESS_KEY") } - if os.Getenv("AWS_DEFAULT_REGION") == "" { - errs = append(errs, fmt.Errorf("AWS_DEFAULT_REGION: %w", ErrMissingEnvVar)) - } else { - oks = append(oks, "AWS_DEFAULT_REGION") + if len(missingAwsVars) > 0 { + errs = append(errs, fmt.Errorf("%w, either AWS_PROFILE or the following environment variables must be set: %s", + ErrMissingEnvVars, strings.Join(missingAwsVars, ", "))) + + return oks, errs } return oks, errs diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 35816263b..c3081aefc 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -212,9 +212,9 @@ var ( Expect(out).To(ContainSubstring("kubectl:")) Expect(out).To(ContainSubstring("kustomize:")) Expect(out).To(ContainSubstring("furyagent:")) - Expect(out).To(ContainSubstring("AWS_ACCESS_KEY_ID:")) - Expect(out).To(ContainSubstring("AWS_SECRET_ACCESS_KEY:")) - Expect(out).To(ContainSubstring("AWS_DEFAULT_REGION:")) + Expect(out).To(ContainSubstring("missing required environment variable: AWS_DEFAULT_REGION")) + Expect(out).To(ContainSubstring("missing environment variables, either AWS_PROFILE or the " + + "following environment variables must be set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")) }) It("should report an error when dependencies are wrong", Serial, func() { @@ -250,9 +250,9 @@ var ( Expect(out).To( ContainSubstring("terraform: wrong tool version - installed = 0.15.3, expected = 0.15.4"), ) - Expect(out).To(ContainSubstring("AWS_ACCESS_KEY_ID: missing environment variable")) - Expect(out).To(ContainSubstring("AWS_SECRET_ACCESS_KEY: missing environment variable")) - Expect(out).To(ContainSubstring("AWS_DEFAULT_REGION: missing environment variable")) + Expect(out).To(ContainSubstring("missing required environment variable: AWS_DEFAULT_REGION")) + Expect(out).To(ContainSubstring("missing environment variables, either AWS_PROFILE or the " + + "following environment variables must be set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")) }) It("should exit without errors when dependencies are correct", Serial, func() { From 96b22de9d80f1fe99f8d690262ffa570fd6658ad Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:09:29 +0100 Subject: [PATCH 130/383] chore: removed warning about cluster reachability when in dry-run (#300) --- .../kfd/v1alpha2/eks/create/distribution.go | 33 +++++++++++-------- internal/apis/kfd/v1alpha2/eks/creator.go | 7 ++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 77af3ddc4..1060ed780 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -56,6 +56,7 @@ type Distribution struct { kzRunner *kustomize.Runner kubeRunner *kubectl.Runner dryRun bool + phase string } type injectType struct { @@ -68,16 +69,17 @@ func NewDistribution( kfdManifest config.KFD, infraOutputsPath string, dryRun bool, + phase string, ) (*Distribution, error) { distroDir := path.Join(paths.WorkDir, cluster.OperationPhaseDistribution) - phase, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, paths.BinPath) + phaseOp, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, paths.BinPath) if err != nil { return nil, fmt.Errorf("error creating distribution phase: %w", err) } return &Distribution{ - OperationPhase: phase, + OperationPhase: phaseOp, furyctlConf: furyctlConf, kfdManifest: kfdManifest, infraOutputsPath: infraOutputsPath, @@ -86,25 +88,25 @@ func NewDistribution( tfRunner: terraform.NewRunner( execx.NewStdExecutor(), terraform.Paths{ - Logs: phase.LogsPath, - Outputs: phase.OutputsPath, - WorkDir: path.Join(phase.Path, "terraform"), - Plan: phase.PlanPath, - Terraform: phase.TerraformPath, + Logs: phaseOp.LogsPath, + Outputs: phaseOp.OutputsPath, + WorkDir: path.Join(phaseOp.Path, "terraform"), + Plan: phaseOp.PlanPath, + Terraform: phaseOp.TerraformPath, }, ), kzRunner: kustomize.NewRunner( execx.NewStdExecutor(), kustomize.Paths{ - Kustomize: phase.KustomizePath, - WorkDir: path.Join(phase.Path, "manifests"), + Kustomize: phaseOp.KustomizePath, + WorkDir: path.Join(phaseOp.Path, "manifests"), }, ), kubeRunner: kubectl.NewRunner( execx.NewStdExecutor(), kubectl.Paths{ - Kubectl: phase.KubectlPath, - WorkDir: path.Join(phase.Path, "manifests"), + Kubectl: phaseOp.KubectlPath, + WorkDir: path.Join(phaseOp.Path, "manifests"), Kubeconfig: paths.Kubeconfig, }, true, @@ -112,6 +114,7 @@ func NewDistribution( false, ), dryRun: dryRun, + phase: phase, }, nil } @@ -131,8 +134,10 @@ func (d *Distribution) Exec() error { return errClusterConnect } - logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + - "running the command without --dry-run") + if d.phase == cluster.OperationPhaseDistribution { + logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + + "running the command without --dry-run") + } } if err := d.CreateFolder(); err != nil { @@ -166,7 +171,7 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error running terraform init: %w", err) } - if err := d.tfRunner.Plan(timestamp); err != nil { + if err := d.tfRunner.Plan(timestamp); err != nil && !d.dryRun { return fmt.Errorf("error running terraform plan: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 2454eb565..5acc673bc 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -242,6 +242,12 @@ func (v *ClusterCreator) allPhases( } } + if v.dryRun { + logrus.Info("Kubernetes Fury cluster created successfully (dry-run mode)") + + return nil + } + logrus.Info("Kubernetes Fury cluster created successfully") if v.furyctlConf.Spec.Infrastructure != nil { @@ -281,6 +287,7 @@ func (v *ClusterCreator) setupPhases() (*create.Infrastructure, *create.Kubernet v.kfdManifest, infra.OutputsPath, v.dryRun, + v.phase, ) if err != nil { return nil, nil, nil, fmt.Errorf("error while initiating distribution phase: %w", err) From 6e3695e8515a9777ac1754d82e583ab8f9f50ce6 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:06:02 +0100 Subject: [PATCH 131/383] fix: added ami to tfvars in nodepools (#305) --- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 72551ea3d..35043251b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -456,6 +456,17 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + if np.Ami != nil { + err = bytesx.SafeWriteToBuffer( + &buffer, + "os = \"%v\"\n", + np.Ami.Id, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + spot := "false" if np.Instance.Spot != nil { From e85dd92fc36baa968dafae1de3d4c33c0e6ecd51 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:57:19 +0100 Subject: [PATCH 132/383] Feat: added terraform state bucket validation (#201) * feat: added tools configuration validation chore: added dummy bucket config fix: e2e tests fix: test e2e check CI fix: CI cannot create/delete buckets in parallel fix: CI drone s3 bucket creation conditional feat: added rich semver handling * feat: switched to terraform syntax in semver fix: unit tests, removed unused func call chore: update all awscli version to wildcard chore: update fury-distribution dep chore: update again dep to fury-distribution chore: upgrade fury-distribution dep chore: removed dummy * fix: switched to correct s3 bucket api * fix: check if bucket exists in drone Co-authored-by: Claudio Beatrice * chore: apply suggestions from code review Co-authored-by: Claudio Beatrice * chore: rename Get* to New* --------- Co-authored-by: Claudio Beatrice --- .drone.yml | 2 + cmd/create/config.go | 11 + cmd/root.go | 16 +- cmd/validate/dependencies.go | 17 +- go.mod | 6 +- go.sum | 11 +- internal/dependencies/tools/tool.go | 13 +- internal/dependencies/tools/validator.go | 12 +- internal/dependencies/tools/validator_test.go | 16 +- internal/dependencies/toolsconf/validator.go | 71 ++++ internal/dependencies/validate.go | 19 +- internal/semver/compare.go | 149 ++------- internal/semver/compare_test.go | 308 +++--------------- internal/tool/awscli/runner.go | 16 + test/data/e2e/create/cluster/bin_mock/aws | 5 - test/data/e2e/create/cluster/data/kfd.yaml | 2 +- test/data/e2e/create/config/distro/kfd.yaml | 2 +- .../dependencies/v1.24.1/distro/kfd.yaml | 2 +- .../data/e2e/validate/config/correct/kfd.yaml | 2 +- test/data/e2e/validate/config/wrong/kfd.yaml | 2 +- .../dependencies/correct/furyctl.yaml | 2 +- .../validate/dependencies/correct/kfd.yaml | 2 +- .../validate/dependencies/missing/kfd.yaml | 2 +- .../e2e/validate/dependencies/wrong/kfd.yaml | 2 +- test/data/expensive/common/data/kfd.yaml | 2 +- test/data/integration/v1.24.1/distro/kfd.yaml | 2 +- test/e2e/furyctl_test.go | 5 +- 27 files changed, 274 insertions(+), 425 deletions(-) create mode 100644 internal/dependencies/toolsconf/validator.go delete mode 100755 test/data/e2e/create/cluster/bin_mock/aws diff --git a/.drone.yml b/.drone.yml index 9f69d1609..0ac2eced7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -95,6 +95,8 @@ steps: - touch /root/.ssh/known_hosts - chmod 600 /root/.ssh/known_hosts - ssh-keyscan -H github.com > /etc/ssh/ssh_known_hosts 2> /dev/null + # Create required s3 bucket if it doesn't exist + - (test ! $(aws s3api get-bucket-location --bucket furyctl-e2e-deps-test --output text --no-cli-pager 2>/dev/null | grep "$${AWS_DEFAULT_REGION}")) && aws s3 mb s3://furyctl-e2e-deps-test --region $${AWS_DEFAULT_REGION} # Run tests - make test-e2e environment: diff --git a/cmd/create/config.go b/cmd/create/config.go index 3b48500da..8eedb8a91 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -86,6 +86,17 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { }, Spec: distroConfig.FuryctlSpec{ DistributionVersion: semver.EnsurePrefix(version), + ToolsConfiguration: distroConfig.ToolsConfiguration{ + Terraform: distroConfig.Terraform{ + State: distroConfig.State{ + S3: distroConfig.S3{ + BucketName: "bucket-name", + KeyPrefix: "key-prefix", + Region: "eu-west-1", + }, + }, + }, + }, }, } diff --git a/cmd/root.go b/cmd/root.go index cfcaefa35..c9c8b191c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -216,7 +216,21 @@ func shouldUpgrade(releaseVersion, currentVersion string) bool { return false } - return semver.Gt(releaseVersion, currentVersion) + relV, err := semver.NewVersion(releaseVersion) + if err != nil { + logrus.Debugf("Error parsing release version: %s", err) + + return false + } + + curV, err := semver.NewVersion(currentVersion) + if err != nil { + logrus.Debugf("Error parsing current version: %s", err) + + return false + } + + return relV.GreaterThan(curV) } func checkUpdates(version string, rc chan app.Release, e chan error) { diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 903bd02a1..b01083323 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -16,6 +16,7 @@ import ( "github.com/sighupio/furyctl/internal/cmd/cmdutil" "github.com/sighupio/furyctl/internal/dependencies/envvars" "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/dependencies/toolsconf" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -74,18 +75,28 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { envVarsValidator := envvars.NewValidator() + toolsConfigValidator := toolsconf.NewValidator(execx.NewStdExecutor()) + errs := make([]error, 0) logrus.Info("Validating tools...") - toks, terrs := toolsValidator.Validate(dres.DistroManifest) + toks, terrs := toolsValidator.Validate( + dres.DistroManifest, + dres.MinimalConf.Spec.ToolsConfiguration.Terraform.State, + ) logrus.Info("Validating environment variables...") eoks, eerrs := envVarsValidator.Validate(dres.MinimalConf.Kind) + logrus.Info("Validating tools configuration...") + + tcoks, tcerrs := toolsConfigValidator.Validate(dres.MinimalConf.Spec.ToolsConfiguration.Terraform.State.S3) + errs = append(errs, terrs...) errs = append(errs, eerrs...) + errs = append(errs, tcerrs...) for _, tok := range toks { logrus.Infof("%s: binary found in vendor folder", tok) @@ -95,6 +106,10 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { logrus.Infof("%s: environment variable found", eok) } + for _, tcok := range tcoks { + logrus.Infof("%s: configured", tcok) + } + if len(errs) > 0 { logrus.Debugf("Repository path: %s", dres.RepoPath) diff --git a/go.mod b/go.mod index 77fa3a393..4596c8634 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/sighupio/furyctl go 1.19 require ( + github.com/Al-Pragliola/go-version v1.6.2 github.com/Masterminds/sprig/v3 v3.2.3 github.com/briandowns/spinner v1.19.0 github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v1.0.1 github.com/hashicorp/go-getter v1.6.2 - github.com/hashicorp/go-version v1.6.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 @@ -25,7 +25,7 @@ require ( github.com/onsi/ginkgo/v2 v2.6.1 github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 - github.com/sighupio/fury-distribution v1.24.1-0.20230125135610-4bbbb88e5ffa + github.com/sighupio/fury-distribution v1.24.1-0.20230213153056-faa332a9b3a8 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) @@ -53,11 +53,13 @@ require ( github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jsonmaur/aws-regions/v2 v2.3.1 // indirect github.com/klauspost/compress v1.15.13 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect diff --git a/go.sum b/go.sum index 4b4c4022d..fd1ed0d63 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Al-Pragliola/go-version v1.6.2 h1:K3smnXe9EQ/o1SrwDoxBcyj28TfT4xGmY5rAxWlHv+g= +github.com/Al-Pragliola/go-version v1.6.2/go.mod h1:G0LEBz1BqdOMWU8y5Izjxwx3LmcYJuX9JBe5wuvLHXo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -189,9 +191,8 @@ github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6p github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -214,6 +215,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jsonmaur/aws-regions/v2 v2.3.1 h1:WWt452LyhjI4ZCRKBSULVHqIGE8/9UqVQOSAzuc2woE= +github.com/jsonmaur/aws-regions/v2 v2.3.1/go.mod h1:NqtmZ2wG5HkrTYFQ+II3BDysj0yek59yjtZjAaCn8lE= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -288,8 +291,8 @@ github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdk github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.24.1-0.20230125135610-4bbbb88e5ffa h1:7TOzkZAqx0VswNGsuqB/VnnegLv0q83qAfvVoO7uA0w= -github.com/sighupio/fury-distribution v1.24.1-0.20230125135610-4bbbb88e5ffa/go.mod h1:MpW/zGMlwJIQoBgZ6xWwL6RN6j7N5bCEX8949p3Pfik= +github.com/sighupio/fury-distribution v1.24.1-0.20230213153056-faa332a9b3a8 h1:Kvgtt/DqZgmMIJUSv01ZbVOc1QOVSPQSoUEvs0h3EGI= +github.com/sighupio/fury-distribution v1.24.1-0.20230213153056-faa332a9b3a8/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/dependencies/tools/tool.go b/internal/dependencies/tools/tool.go index cef0f448b..50c5e68c0 100644 --- a/internal/dependencies/tools/tool.go +++ b/internal/dependencies/tools/tool.go @@ -11,6 +11,7 @@ import ( "path/filepath" "regexp" + "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/tool" "github.com/sighupio/furyctl/internal/tool/ansible" "github.com/sighupio/furyctl/internal/tool/awscli" @@ -172,7 +173,17 @@ func (vc *checker) version(want string) error { systemVersion := vc.trimFn(versionStringTokens) - if systemVersion != want { + sysVer, err := semver.NewVersion(systemVersion) + if err != nil { + return fmt.Errorf("%w: %v", errGetVersion, err) + } + + wantVer, err := semver.NewConstraint(want) + if err != nil { + return fmt.Errorf("%w: %v", errGetVersion, err) + } + + if !wantVer.Check(sysVer) { return fmt.Errorf("%w - installed = %s, expected = %s", ErrWrongToolVersion, systemVersion, want) } diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index b98d4ba93..770e93d83 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/sighupio/fury-distribution/pkg/config" + itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -32,7 +33,7 @@ type Validator struct { toolFactory *Factory } -func (tv *Validator) Validate(kfdManifest config.KFD) ([]string, []error) { +func (tv *Validator) Validate(kfdManifest config.KFD, tfState config.State) ([]string, []error) { var ( oks []string errs []error @@ -58,5 +59,14 @@ func (tv *Validator) Validate(kfdManifest config.KFD) ([]string, []error) { } } + if tfState.S3.BucketName != "" { + tool := tv.toolFactory.Create(itool.Awscli, "*") + if err := tool.CheckBinVersion(); err != nil { + errs = append(errs, err) + } else { + oks = append(oks, "aws") + } + } + return oks, errs } diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 6c57c84f0..3b4e7ee93 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -20,6 +20,7 @@ func Test_Validator_Validate(t *testing.T) { testCases := []struct { desc string manifest config.KFD + state config.State wantOks []string wantErrs []error }{ @@ -35,11 +36,17 @@ func Test_Validator_Validate(t *testing.T) { }, }, }, + state: config.State{ + S3: config.S3{ + BucketName: "test", + }, + }, wantOks: []string{ "kubectl", "kustomize", "terraform", "furyagent", + "aws", }, }, { @@ -54,13 +61,18 @@ func Test_Validator_Validate(t *testing.T) { }, }, }, + state: config.State{ + S3: config.S3{ + BucketName: "test", + }, + }, wantErrs: []error{ errors.New("furyagent: wrong tool version - installed = 0.3.0, expected = 0.4.0"), errors.New("kubectl: wrong tool version - installed = 1.21.1, expected = 1.22.0"), errors.New("kustomize: wrong tool version - installed = 3.9.4, expected = 3.5.3"), errors.New("terraform: wrong tool version - installed = 0.15.4, expected = 1.3.0"), }, - wantOks: []string{}, + wantOks: []string{"aws"}, }, } for _, tC := range testCases { @@ -69,7 +81,7 @@ func Test_Validator_Validate(t *testing.T) { t.Run(tC.desc, func(t *testing.T) { v := tools.NewValidator(execx.NewFakeExecutor(), "test_data") - oks, errs := v.Validate(tC.manifest) + oks, errs := v.Validate(tC.manifest, tC.state) if len(oks) != len(tC.wantOks) { t.Errorf("Expected %d oks, got %d - %v", len(tC.wantOks), len(oks), oks) diff --git a/internal/dependencies/toolsconf/validator.go b/internal/dependencies/toolsconf/validator.go new file mode 100644 index 000000000..a0142723f --- /dev/null +++ b/internal/dependencies/toolsconf/validator.go @@ -0,0 +1,71 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package toolsconf + +import ( + "errors" + "fmt" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/tool/awscli" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +var ( + ErrAWSS3BucketNotFound = errors.New("AWS S3 Bucket not found, please create it before running furyctl") + ErrAWSS3BucketRegionMismatch = errors.New("AWS S3 Bucket region mismatch") +) + +func NewValidator(executor execx.Executor) *Validator { + return &Validator{ + awsCliRunner: awscli.NewRunner( + executor, + awscli.Paths{ + Awscli: "aws", + WorkDir: "", + }, + ), + } +} + +type Validator struct { + awsCliRunner *awscli.Runner +} + +func (v *Validator) Validate(s3Conf config.S3) ([]string, []error) { + return v.checkAWSS3Bucket(s3Conf.BucketName, s3Conf.Region) +} + +func (v *Validator) checkAWSS3Bucket(bucketName, region string) ([]string, []error) { + oks := make([]string, 0) + errs := make([]error, 0) + + r, err := v.awsCliRunner.S3Api("get-bucket-location", "--bucket", bucketName, "--output", "text") + if err != nil { + logrus.Debug(fmt.Errorf("error checking AWS S3 Bucket: %w", err)) + + errs = append(errs, ErrAWSS3BucketNotFound) + + return oks, errs + } + + // AWS S3 Bucket in us-east-1 region returns None as LocationConstraint + // https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-location.html#output + if r == "None" { + r = "us-east-1" + } + + if r != region { + errs = append(errs, fmt.Errorf("%w, expected %s, got %s", ErrAWSS3BucketRegionMismatch, region, r)) + + return oks, errs + } + + oks = append(oks, "AWS S3 Bucket") + + return oks, errs +} diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index 4d8df4ac0..af0958c45 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -10,29 +10,36 @@ import ( "github.com/sighupio/furyctl/internal/dependencies/envvars" "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/dependencies/toolsconf" "github.com/sighupio/furyctl/internal/distribution" execx "github.com/sighupio/furyctl/internal/x/exec" ) var ( - errValidatingTools = errors.New("errors validating tools") - errValidatingEnv = errors.New("errors validating env vars") + errValidatingTools = errors.New("errors validating tools") + errValidatingEnv = errors.New("errors validating env vars") + errValidatingToolsConf = errors.New("errors validating tools configuration") ) func NewValidator(executor execx.Executor, binPath string) *Validator { return &Validator{ toolsValidator: tools.NewValidator(executor, binPath), envVarsValidator: envvars.NewValidator(), + infraValidator: toolsconf.NewValidator(executor), } } type Validator struct { toolsValidator *tools.Validator envVarsValidator *envvars.Validator + infraValidator *toolsconf.Validator } func (v *Validator) Validate(res distribution.DownloadResult) error { - if _, errs := v.toolsValidator.Validate(res.DistroManifest); len(errs) > 0 { + if _, errs := v.toolsValidator.Validate( + res.DistroManifest, + res.MinimalConf.Spec.ToolsConfiguration.Terraform.State, + ); len(errs) > 0 { return fmt.Errorf("%w: %v", errValidatingTools, errs) } @@ -40,5 +47,11 @@ func (v *Validator) Validate(res distribution.DownloadResult) error { return fmt.Errorf("%w: %v", errValidatingEnv, errs) } + if _, errs := v.infraValidator.Validate( + res.MinimalConf.Spec.ToolsConfiguration.Terraform.State.S3, + ); len(errs) > 0 { + return fmt.Errorf("%w: %v", errValidatingToolsConf, errs) + } + return nil } diff --git a/internal/semver/compare.go b/internal/semver/compare.go index aaeb73e3a..dff1599f0 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -6,150 +6,43 @@ package semver import ( "fmt" - "regexp" - "strconv" - "strings" + + "github.com/Al-Pragliola/go-version" ) var ( - // Link: https://regex101.com/r/Ly7O1x/3/ - //nolint:lll //We can't wrap regex - regex = regexp.MustCompile(`^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) - - ErrInvalidSemver = fmt.Errorf("invalid semantic version") + // ErrInvalidVersion is returned when the version is not valid. + ErrInvalidVersion = fmt.Errorf("invalid version") + // ErrInvalidConstraint is returned when the constraint is not valid. + ErrInvalidConstraint = fmt.Errorf("invalid constraint") ) -// NewVersion takes a string and returns a Version. -func NewVersion(v string) (Version, error) { - if !isValid(v) { - return "", fmt.Errorf("%w: %s", ErrInvalidSemver, v) - } - - return Version(v), nil -} - -type Version string - -func (s Version) String() string { - return string(s) -} - -// SamePatchStr takes two version strings and tell if they match down to patch level. -func SamePatchStr(a, b string) bool { - return SamePatch(Version(a), Version(b)) -} - -// SamePatch takes two versions and tell if they are part of the same patch. -func SamePatch(a, b Version) bool { - if a == b { - return true - } - - aMajor, aMinor, aPatch, _ := Parts(a.String()) - bMajor, bMinor, bPatch, _ := Parts(b.String()) +func NewVersion(v string) (*version.Version, error) { + vStr := v - return aMajor == bMajor && aMinor == bMinor && aPatch == bPatch -} - -// SameMinorStr takes two version strings and tell if they match down to minor level. -func SameMinorStr(a, b string) bool { - return SameMinor(Version(a), Version(b)) -} - -// SameMinor takes two versions and tell if they are part of the same minor. -func SameMinor(a, b Version) bool { - if a == b { - return true + if v[0] == 'v' { + vStr = v[1:] } - aMajor, aMinor, _, _ := Parts(a.String()) - bMajor, bMinor, _, _ := Parts(b.String()) - - return aMajor == bMajor && aMinor == bMinor -} - -// Gt returns true if a is greater than b. -func Gt(va, vb string) bool { - if va == vb { - return false - } - - aMajor, aMinor, aPatch, _ := Parts(va) - bMajor, bMinor, bPatch, _ := Parts(vb) - - if aMajor > bMajor { - return true - } - - if aMajor < bMajor { - return false - } - - if aMinor > bMinor { - return true - } - - if aMinor < bMinor { - return false - } - - if aPatch > bPatch { - return true - } - - if aPatch < bPatch { - return false + ver, err := version.NewVersion(vStr) + if err != nil { + return nil, ErrInvalidVersion } - return false + return ver, nil } -// Parts returns the major, minor, patch and buil+prerelease parts of a version. -func Parts(v string) (int, int, int, string) { - pv := EnsurePrefix(v) - - if !isValid(pv) { - return 0, 0, 0, "" - } - - parts := strings.Split(EnsureNoPrefix(v), ".") - - ch := "-" - m := strings.Index(v, "-") - p := strings.Index(v, "+") +func NewConstraint(c string) (version.Constraints, error) { + cStr := c - if (m == -1 && p > -1) || (m > -1 && p > -1 && p < m) { - ch = "+" + if c[0] == 'v' { + cStr = c[1:] } - patchParts := strings.Split(strings.Join(parts[2:], "."), ch) - - major, err := strconv.Atoi(parts[0]) + cnst, err := version.NewConstraint(cStr) if err != nil { - return 0, 0, 0, "" - } - - minor, err := strconv.Atoi(parts[1]) - if err != nil { - return 0, 0, 0, "" - } - - patch, err := strconv.Atoi(patchParts[0]) - if err != nil { - return 0, 0, 0, "" - } - - if len(patchParts) > 1 { - return major, minor, patch, strings.Join(patchParts[1:], ch) - } - - return major, minor, patch, "" -} - -func isValid(v string) bool { - if v[0] != 'v' { - return false + return nil, ErrInvalidConstraint } - return regex.Match([]byte(v[1:])) + return cnst, nil } diff --git a/internal/semver/compare_test.go b/internal/semver/compare_test.go index 8849b504a..e8cac4abc 100644 --- a/internal/semver/compare_test.go +++ b/internal/semver/compare_test.go @@ -9,12 +9,12 @@ package semver_test import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/sighupio/furyctl/internal/semver" ) -func Test_NewVersion(t *testing.T) { +func TestGetVersion(t *testing.T) { + t.Parallel() + tests := []struct { name string version string @@ -46,14 +46,14 @@ func Test_NewVersion(t *testing.T) { wantErr: false, }, { - name: "invalid v-less version", + name: "valid v-less version", version: "1.2.3", - wantErr: true, + wantErr: false, }, { - name: "invalid numeric version", + name: "valid numeric version", version: "11", - wantErr: true, + wantErr: false, }, { name: "invalid string version", @@ -61,295 +61,79 @@ func Test_NewVersion(t *testing.T) { wantErr: true, }, } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + _, err := semver.NewVersion(tt.version) if (err != nil) != tt.wantErr { t.Errorf("NewVersion() error = %v, wantErr %v", err, tt.wantErr) + return } }) } } -func TestGt(t *testing.T) { +func TestGetConstraint(t *testing.T) { t.Parallel() - testCases := []struct { - desc string - a string - b string - want bool + tests := []struct { + name string + constraint string + wantErr bool }{ { - desc: "a equals b", - a: "0.1.0", - b: "0.1.0", - want: false, - }, - { - desc: "a lesser patch than b", - a: "0.1.0", - b: "0.1.1", - want: false, + name: "valid constraint", + constraint: "v1.2.3", + wantErr: false, }, { - desc: "a greater patch than b", - a: "0.1.1", - b: "0.1.0", - want: true, + name: "valid v-less constraint", + constraint: "1.2.3", + wantErr: false, }, { - desc: "a lesser minor than b", - a: "0.1.0", - b: "0.2.0", - want: false, + name: "valid constraint with greater than", + constraint: ">1.2.3", + wantErr: false, }, { - desc: "a greater minor than b", - a: "0.2.0", - b: "0.1.0", - want: true, + name: "valid constraint with greater or equal than", + constraint: ">=1.2.3", + wantErr: false, }, { - desc: "a lesser major than b", - a: "0.1.0", - b: "1.1.0", - want: false, + name: "valid constraint with pessimistic", + constraint: "~>1.2.3", + wantErr: false, }, { - desc: "a greater major than b", - a: "1.1.0", - b: "0.1.0", - want: true, + name: "valid constraint with wildcard", + constraint: "*", + wantErr: false, }, { - desc: "a lesser major, greater minor and patch than b", - a: "0.2.2", - b: "1.0.0", - want: false, + name: "invalid constraint", + constraint: ">1.", + wantErr: true, }, - { - desc: "a lesser minor, greater patch than b", - a: "0.2.2", - b: "0.3.0", - want: false, - }, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - t.Parallel() - - if got := semver.Gt(tC.a, tC.b); got != tC.want { - t.Errorf("Gt() = %v, want %v", got, tC.want) - } - }) - } -} - -func TestParts(t *testing.T) { - t.Parallel() - - testCases := []struct { - desc string - version string - want []any - }{ - {desc: "0.0.4", want: []any{0, 0, 4, ""}}, - {desc: "1.2.3", want: []any{1, 2, 3, ""}}, - {desc: "10.20.30", want: []any{10, 20, 30, ""}}, - {desc: "1.1.2-prerelease+meta", want: []any{1, 1, 2, "prerelease+meta"}}, - {desc: "1.1.2+meta", want: []any{1, 1, 2, "meta"}}, - {desc: "1.1.2+meta-valid", want: []any{1, 1, 2, "meta-valid"}}, - {desc: "1.0.0-alpha", want: []any{1, 0, 0, "alpha"}}, - {desc: "1.0.0-beta", want: []any{1, 0, 0, "beta"}}, - {desc: "1.0.0-alpha.beta", want: []any{1, 0, 0, "alpha.beta"}}, - {desc: "1.0.0-alpha.beta.1", want: []any{1, 0, 0, "alpha.beta.1"}}, - {desc: "1.0.0-alpha.1", want: []any{1, 0, 0, "alpha.1"}}, - {desc: "1.0.0-alpha0.valid", want: []any{1, 0, 0, "alpha0.valid"}}, - {desc: "1.0.0-alpha.0valid", want: []any{1, 0, 0, "alpha.0valid"}}, - {desc: "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", want: []any{1, 0, 0, "alpha-a.b-c-somethinglong+build.1-aef.1-its-okay"}}, - {desc: "1.0.0-rc.1+build.1", want: []any{1, 0, 0, "rc.1+build.1"}}, - {desc: "2.0.0-rc.1+build.123", want: []any{2, 0, 0, "rc.1+build.123"}}, - {desc: "1.2.3-beta", want: []any{1, 2, 3, "beta"}}, - {desc: "10.2.3-DEV-SNAPSHOT", want: []any{10, 2, 3, "DEV-SNAPSHOT"}}, - {desc: "1.2.3-SNAPSHOT-123", want: []any{1, 2, 3, "SNAPSHOT-123"}}, - {desc: "1.0.0", want: []any{1, 0, 0, ""}}, - {desc: "2.0.0", want: []any{2, 0, 0, ""}}, - {desc: "1.1.7", want: []any{1, 1, 7, ""}}, - {desc: "2.0.0+build.1848", want: []any{2, 0, 0, "build.1848"}}, - {desc: "2.0.1-alpha.1227", want: []any{2, 0, 1, "alpha.1227"}}, - {desc: "1.0.0-alpha+beta", want: []any{1, 0, 0, "alpha+beta"}}, - {desc: "1.2.3----RC-SNAPSHOT.12.9.1--.12+788", want: []any{1, 2, 3, "---RC-SNAPSHOT.12.9.1--.12+788"}}, - {desc: "1.2.3----R-S.12.9.1--.12+meta", want: []any{1, 2, 3, "---R-S.12.9.1--.12+meta"}}, - {desc: "1.2.3----RC-SNAPSHOT.12.9.1--.12", want: []any{1, 2, 3, "---RC-SNAPSHOT.12.9.1--.12"}}, - {desc: "1.0.0+0.build.1-rc.10000aaa-kk-0.1", want: []any{1, 0, 0, "0.build.1-rc.10000aaa-kk-0.1"}}, - {desc: "999999999999999999.999999999999999999.99999999999999999", want: []any{999999999999999999, 999999999999999999, 99999999999999999, ""}}, - {desc: "1.0.0-0A.is.legal", want: []any{1, 0, 0, "0A.is.legal"}}, - - {desc: "1", want: []any{0, 0, 0, ""}}, - {desc: "1.2", want: []any{0, 0, 0, ""}}, - {desc: "1.2.3-0123", want: []any{0, 0, 0, ""}}, - {desc: "1.2.3-0123.0123", want: []any{0, 0, 0, ""}}, - {desc: "1.1.2+.123", want: []any{0, 0, 0, ""}}, - {desc: "+invalid", want: []any{0, 0, 0, ""}}, - {desc: "-invalid", want: []any{0, 0, 0, ""}}, - {desc: "-invalid+invalid", want: []any{0, 0, 0, ""}}, - {desc: "-invalid.01", want: []any{0, 0, 0, ""}}, - {desc: "alpha", want: []any{0, 0, 0, ""}}, - {desc: "alpha.beta", want: []any{0, 0, 0, ""}}, - {desc: "alpha.beta.1", want: []any{0, 0, 0, ""}}, - {desc: "alpha.1", want: []any{0, 0, 0, ""}}, - {desc: "alpha+beta", want: []any{0, 0, 0, ""}}, - {desc: "alpha_beta", want: []any{0, 0, 0, ""}}, - {desc: "alpha.", want: []any{0, 0, 0, ""}}, - {desc: "alpha..", want: []any{0, 0, 0, ""}}, - {desc: "beta", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha_beta", want: []any{0, 0, 0, ""}}, - {desc: "-alpha.", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha..", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha..1", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha...1", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha....1", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha.....1", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha......1", want: []any{0, 0, 0, ""}}, - {desc: "1.0.0-alpha.......1", want: []any{0, 0, 0, ""}}, - {desc: "01.1.1", want: []any{0, 0, 0, ""}}, - {desc: "1.01.1", want: []any{0, 0, 0, ""}}, - {desc: "1.1.01", want: []any{0, 0, 0, ""}}, - {desc: "1.2", want: []any{0, 0, 0, ""}}, - {desc: "1.2.3.DEV", want: []any{0, 0, 0, ""}}, - {desc: "1.2-SNAPSHOT", want: []any{0, 0, 0, ""}}, - {desc: "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788", want: []any{0, 0, 0, ""}}, - {desc: "1.2-RC-SNAPSHOT", want: []any{0, 0, 0, ""}}, - {desc: "-1.0.3-gamma+b7718", want: []any{0, 0, 0, ""}}, - {desc: "+justmeta", want: []any{0, 0, 0, ""}}, - {desc: "9.8.7+meta+meta", want: []any{0, 0, 0, ""}}, - {desc: "9.8.7-whatever+meta+meta", want: []any{0, 0, 0, ""}}, - {desc: "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12", want: []any{0, 0, 0, ""}}, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - t.Parallel() - - major, minor, patch, rest := semver.Parts(tC.desc) - - parts := []any{major, minor, patch, rest} - - if !cmp.Equal(parts, tC.want) { - t.Errorf("parts mismatch (-want +got):\n%s", cmp.Diff(tC.want, parts)) - } - }) } -} -func TestSamePatchStr(t *testing.T) { - t.Parallel() - - testCases := []struct { - desc string - a string - b string - want bool - }{ - { - desc: "same patch, different build using dash", - a: "v1.2.3-1", - b: "v1.2.3-2", - want: true, - }, - { - desc: "same patch, different build using plus", - a: "v1.2.3+b1", - b: "v1.2.3+b2", - want: true, - }, - { - desc: "same patch", - a: "v1.2.3", - b: "v1.2.3", - want: true, - }, - { - desc: "same minor", - a: "v1.2.3", - b: "v1.2.4", - want: false, - }, - { - desc: "same major", - a: "v1.2.3", - b: "v1.3.4", - want: false, - }, - } - for _, tC := range testCases { - tC := tC + for _, tt := range tests { + tt := tt - t.Run(tC.desc, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := semver.SamePatchStr(tC.a, tC.b) - if got != tC.want { - t.Errorf("want = %t, got = %t", tC.want, got) - } - }) - } -} - -func TesSameMinorStr(t *testing.T) { - t.Parallel() - - testCases := []struct { - desc string - a string - b string - want bool - }{ - { - desc: "same patch, different build using dash", - a: "v1.2.3-1", - b: "v1.2.3-2", - want: true, - }, - { - desc: "same patch, different build using plus", - a: "v1.2.3+b1", - b: "v1.2.3+b2", - want: true, - }, - { - desc: "same patch", - a: "v1.2.3", - b: "v1.2.3", - want: true, - }, - { - desc: "same minor", - a: "v1.2.3", - b: "v1.2.4", - want: true, - }, - { - desc: "same major", - a: "v1.2.3", - b: "v1.3.4", - want: false, - }, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - t.Parallel() + _, err := semver.NewConstraint(tt.constraint) + if (err != nil) != tt.wantErr { + t.Errorf("NewConstraint() error = %v, wantErr %v", err, tt.wantErr) - got := semver.SameMinorStr(tC.a, tC.b) - if got != tC.want { - t.Errorf("want = %t, got = %t", tC.want, got) + return } }) } diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index 66491e90e..999de5681 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -50,6 +50,22 @@ func (r *Runner) Ec2(sub string, params ...string) (string, error) { return out, nil } +func (r *Runner) S3Api(params ...string) (string, error) { + args := []string{"s3api"} + args = append(args, params...) + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error executing awscli s3api: %w", err) + } + + return out, nil +} + func (r *Runner) Version() (string, error) { out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ Args: []string{"--version"}, diff --git a/test/data/e2e/create/cluster/bin_mock/aws b/test/data/e2e/create/cluster/bin_mock/aws deleted file mode 100755 index 45ec3ca6d..000000000 --- a/test/data/e2e/create/cluster/bin_mock/aws +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -cat < Date: Thu, 23 Feb 2023 17:47:14 +0100 Subject: [PATCH 133/383] chore: introduce private/public split of schemas (#285) * chore: introduce private/public split of schemas * chore: remove commented code * chore: resolve conflicts on go.* --------- Co-authored-by: Alessio Pragliola --- go.mod | 6 +- go.sum | 7 +- .../kfd/v1alpha2/eks/create/distribution.go | 60 +-- .../kfd/v1alpha2/eks/create/infrastructure.go | 6 +- .../kfd/v1alpha2/eks/create/kubernetes.go | 20 +- internal/apis/kfd/v1alpha2/eks/creator.go | 6 +- .../kfd/v1alpha2/eks/delete/infrastructure.go | 6 +- .../kfd/v1alpha2/eks/delete/kubernetes.go | 6 +- internal/apis/kfd/v1alpha2/eks/deleter.go | 6 +- internal/apis/kfd/v1alpha2/eks/init.go | 6 +- internal/config/validate.go | 2 +- internal/distribution/download.go | 4 - internal/distribution/path.go | 8 +- internal/distribution/path_test.go | 42 +- .../{ => public}/ekscluster-kfd-v1alpha2.json | 0 .../config/default/data/expected-furyctl.yaml | 451 ++++++++++++------ .../config/ekscluster-kfd-v1alpha2.yaml.tpl | 451 ++++++++++++------ .../{ => public}/ekscluster-kfd-v1alpha2.json | 0 .../{ => public}/ekscluster-kfd-v1alpha2.json | 0 .../{ => public}/ekscluster-kfd-v1alpha2.json | 0 .../{ => public}/ekscluster-kfd-v1alpha2.json | 0 .../{ => public}/ekscluster-kfd-v1alpha2.json | 0 22 files changed, 707 insertions(+), 380 deletions(-) rename test/data/e2e/create/cluster/data/schemas/{ => public}/ekscluster-kfd-v1alpha2.json (100%) rename test/data/e2e/download/dependencies/v1.24.1/distro/schemas/{ => public}/ekscluster-kfd-v1alpha2.json (100%) rename test/data/e2e/validate/config/correct/schemas/{ => public}/ekscluster-kfd-v1alpha2.json (100%) rename test/data/e2e/validate/config/wrong/schemas/{ => public}/ekscluster-kfd-v1alpha2.json (100%) rename test/data/expensive/common/data/schemas/{ => public}/ekscluster-kfd-v1alpha2.json (100%) rename test/data/integration/v1.24.1/distro/schemas/{ => public}/ekscluster-kfd-v1alpha2.json (100%) diff --git a/go.mod b/go.mod index 4596c8634..921922589 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/sighupio/furyctl go 1.19 require ( - github.com/Al-Pragliola/go-version v1.6.2 github.com/Masterminds/sprig/v3 v3.2.3 github.com/briandowns/spinner v1.19.0 github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v1.0.1 github.com/hashicorp/go-getter v1.6.2 + github.com/hashicorp/go-version v1.6.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 @@ -18,6 +18,7 @@ require ( ) require ( + github.com/Al-Pragliola/go-version v1.6.2 github.com/go-playground/validator/v10 v10.11.1 github.com/google/go-cmp v0.5.9 github.com/hashicorp/terraform-json v0.14.0 @@ -25,7 +26,7 @@ require ( github.com/onsi/ginkgo/v2 v2.6.1 github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 - github.com/sighupio/fury-distribution v1.24.1-0.20230213153056-faa332a9b3a8 + github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) @@ -53,7 +54,6 @@ require ( github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect diff --git a/go.sum b/go.sum index fd1ed0d63..fe2d7585a 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,9 @@ github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6p github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -291,8 +292,8 @@ github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdk github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.24.1-0.20230213153056-faa332a9b3a8 h1:Kvgtt/DqZgmMIJUSv01ZbVOc1QOVSPQSoUEvs0h3EGI= -github.com/sighupio/fury-distribution v1.24.1-0.20230213153056-faa332a9b3a8/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061 h1:kMlliGNS4qbnPcW7CaecQgS1Ye8kA/eXdslI4ilrzjU= +github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 1060ed780..a72a8ee21 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" @@ -48,7 +48,7 @@ var ( type Distribution struct { *cluster.OperationPhase furyctlConfPath string - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD infraOutputsPath string distroPath string @@ -60,12 +60,12 @@ type Distribution struct { } type injectType struct { - Data schema.SpecDistribution `json:"data"` + Data private.SpecDistribution `json:"data"` } func NewDistribution( paths cluster.CreatorPaths, - furyctlConf schema.EksclusterKfdV1Alpha2, + furyctlConf private.EksclusterKfdV1Alpha2, kfdManifest config.KFD, infraOutputsPath string, dryRun bool, @@ -284,11 +284,11 @@ func (d *Distribution) injectDataPreTf(fMerger *merge.Merger) (*merge.Merger, er } injectData := injectType{ - Data: schema.SpecDistribution{ - Modules: schema.SpecDistributionModules{ - Ingress: schema.SpecDistributionModulesIngress{ - Dns: schema.SpecDistributionModulesIngressDNS{ - Private: schema.SpecDistributionModulesIngressDNSPrivate{ + Data: private.SpecDistribution{ + Modules: private.SpecDistributionModules{ + Ingress: private.SpecDistributionModulesIngress{ + Dns: private.SpecDistributionModulesIngressDNS{ + Private: private.SpecDistributionModulesIngressDNSPrivate{ VpcId: vpcID, }, }, @@ -356,36 +356,36 @@ func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, e } injectData := injectType{ - Data: schema.SpecDistribution{ - Modules: schema.SpecDistributionModules{ - Aws: &schema.SpecDistributionModulesAws{ - EbsCsiDriver: &schema.SpecDistributionModulesAwsEbsCsiDriver{ - IamRoleArn: schema.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), + Data: private.SpecDistribution{ + Modules: private.SpecDistributionModules{ + Aws: &private.SpecDistributionModulesAws{ + EbsCsiDriver: &private.SpecDistributionModulesAwsEbsCsiDriver{ + IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), }, - LoadBalancerController: &schema.SpecDistributionModulesAwsLoadBalancerController{ - IamRoleArn: schema.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), + LoadBalancerController: &private.SpecDistributionModulesAwsLoadBalancerController{ + IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), }, - ClusterAutoscaler: &schema.SpecDistributionModulesAwsClusterAutoScaler{ - IamRoleArn: schema.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), + ClusterAutoscaler: &private.SpecDistributionModulesAwsClusterAutoScaler{ + IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), }, }, - Ingress: schema.SpecDistributionModulesIngress{ - ExternalDns: &schema.SpecDistributionModulesIngressExternalDNS{ - PrivateIamRoleArn: schema.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), - PublicIamRoleArn: schema.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), + Ingress: private.SpecDistributionModulesIngress{ + ExternalDns: &private.SpecDistributionModulesIngressExternalDNS{ + PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), + PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), }, - CertManager: &schema.SpecDistributionModulesIngressCertManager{ - ClusterIssuer: schema.SpecDistributionModulesIngressCertManagerClusterIssuer{ - Route53: &schema.SpecDistributionModulesIngressClusterIssuerRoute53{ - IamRoleArn: schema.TypesAwsArn(arns["cert_manager_iam_role_arn"]), + CertManager: &private.SpecDistributionModulesIngressCertManager{ + ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ + Route53: &private.SpecDistributionModulesIngressClusterIssuerRoute53{ + IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), }, }, }, }, - Dr: schema.SpecDistributionModulesDr{ - Velero: schema.SpecDistributionModulesDrVelero{ - Eks: schema.SpecDistributionModulesDrVeleroEks{ - IamRoleArn: schema.TypesAwsArn(arns["velero_iam_role_arn"]), + Dr: private.SpecDistributionModulesDr{ + Velero: private.SpecDistributionModulesDrVelero{ + Eks: private.SpecDistributionModulesDrVeleroEks{ + IamRoleArn: private.TypesAwsArn(arns["velero_iam_role_arn"]), }, }, }, diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index acabd5bff..3594ad068 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -19,7 +19,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" @@ -42,7 +42,7 @@ var ( type Infrastructure struct { *cluster.OperationPhase - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD tfRunner *terraform.Runner faRunner *furyagent.Runner @@ -51,7 +51,7 @@ type Infrastructure struct { } func NewInfrastructure( - furyctlConf schema.EksclusterKfdV1Alpha2, + furyctlConf private.EksclusterKfdV1Alpha2, kfdManifest config.KFD, paths cluster.CreatorPaths, dryRun bool, diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 35043251b..a12db9112 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -20,7 +20,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" @@ -57,7 +57,7 @@ const ( type Kubernetes struct { *cluster.OperationPhase - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD infraOutputsPath string tfRunner *terraform.Runner @@ -66,7 +66,7 @@ type Kubernetes struct { } func NewKubernetes( - furyctlConf schema.EksclusterKfdV1Alpha2, + furyctlConf private.EksclusterKfdV1Alpha2, kfdManifest config.KFD, infraOutputsPath string, paths cluster.CreatorPaths, @@ -246,7 +246,7 @@ func (k *Kubernetes) copyFromTemplate() error { func (k *Kubernetes) createTfVars() error { var buffer bytes.Buffer - var allowedCidrsSource []schema.TypesCidr + var allowedCidrsSource []private.TypesCidr subnetIdsSource := k.furyctlConf.Spec.Kubernetes.SubnetIds vpcIDSource := k.furyctlConf.Spec.Kubernetes.VpcId @@ -286,7 +286,7 @@ func (k *Kubernetes) createTfVars() error { return errVpcCIDRFromOut } - subs := make([]schema.TypesAwsSubnetId, len(s)) + subs := make([]private.TypesAwsSubnetId, len(s)) for i, sub := range s { ss, ok := sub.(string) @@ -294,13 +294,13 @@ func (k *Kubernetes) createTfVars() error { return errPvtSubnetFromOut } - subs[i] = schema.TypesAwsSubnetId(ss) + subs[i] = private.TypesAwsSubnetId(ss) } subnetIdsSource = subs - vpcID := schema.TypesAwsVpcId(v) + vpcID := private.TypesAwsVpcId(v) vpcIDSource = &vpcID - allowedCidrsSource = []schema.TypesCidr{schema.TypesCidr(c)} + allowedCidrsSource = []private.TypesCidr{private.TypesCidr(c)} } } @@ -347,7 +347,7 @@ func (k *Kubernetes) createTfVars() error { return errVpcIDNotFound } - vpcIDSource = new(schema.TypesAwsVpcId) + vpcIDSource = new(private.TypesAwsVpcId) } err = bytesx.SafeWriteToBuffer( @@ -763,7 +763,7 @@ func (k *Kubernetes) addAwsAuthToTfVars(buffer *bytes.Buffer) error { return nil } -func (*Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np schema.SpecKubernetesNodePool) error { +func (*Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np private.SpecKubernetesNodePool) error { if len(np.AdditionalFirewallRules) > 0 { err := bytesx.SafeWriteToBuffer( buffer, diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 5acc673bc..6a9001cb8 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -15,7 +15,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/create" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/kubectl" @@ -31,7 +31,7 @@ var ( type ClusterCreator struct { paths cluster.CreatorPaths - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD phase string vpnAutoConnect bool @@ -54,7 +54,7 @@ func (v *ClusterCreator) SetProperty(name string, value any) { } case cluster.CreatorPropertyFuryctlConf: - if s, ok := value.(schema.EksclusterKfdV1Alpha2); ok { + if s, ok := value.(private.EksclusterKfdV1Alpha2); ok { v.furyctlConf = s } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index 1f6962f0a..a5f737feb 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -12,7 +12,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -22,13 +22,13 @@ import ( type Infrastructure struct { *cluster.OperationPhase - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 tfRunner *terraform.Runner dryRun bool } func NewInfrastructure( - furyctlConf schema.EksclusterKfdV1Alpha2, + furyctlConf private.EksclusterKfdV1Alpha2, dryRun bool, workDir, binPath string, diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index a0e1014b9..1d8fa684e 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -14,7 +14,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" @@ -40,14 +40,14 @@ var ( type Kubernetes struct { *cluster.OperationPhase - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 tfRunner *terraform.Runner awsRunner *awscli.Runner dryRun bool } func NewKubernetes( - furyctlConf schema.EksclusterKfdV1Alpha2, + furyctlConf private.EksclusterKfdV1Alpha2, dryRun bool, workDir, binPath string, diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 4063d5c76..3a9f49be7 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -11,14 +11,14 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" del "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/delete" "github.com/sighupio/furyctl/internal/cluster" ) type ClusterDeleter struct { kfdManifest config.KFD - furyctlConf schema.EksclusterKfdV1Alpha2 + furyctlConf private.EksclusterKfdV1Alpha2 phase string workDir string binPath string @@ -42,7 +42,7 @@ func (d *ClusterDeleter) SetProperty(name string, value any) { } case cluster.DeleterPropertyFuryctlConf: - if s, ok := value.(schema.EksclusterKfdV1Alpha2); ok { + if s, ok := value.(private.EksclusterKfdV1Alpha2); ok { d.furyctlConf = s } diff --git a/internal/apis/kfd/v1alpha2/eks/init.go b/internal/apis/kfd/v1alpha2/eks/init.go index e3daf14f4..b34a5e7a3 100644 --- a/internal/apis/kfd/v1alpha2/eks/init.go +++ b/internal/apis/kfd/v1alpha2/eks/init.go @@ -5,7 +5,7 @@ package eks import ( - "github.com/sighupio/fury-distribution/pkg/schema" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/cluster" ) @@ -14,12 +14,12 @@ func init() { cluster.RegisterCreatorFactory( "kfd.sighup.io/v1alpha2", "EKSCluster", - cluster.NewCreatorFactory[*ClusterCreator, schema.EksclusterKfdV1Alpha2](&ClusterCreator{}), + cluster.NewCreatorFactory[*ClusterCreator, private.EksclusterKfdV1Alpha2](&ClusterCreator{}), ) cluster.RegisterDeleterFactory( "kfd.sighup.io/v1alpha2", "EKSCluster", - cluster.NewDeleterFactory[*ClusterDeleter, schema.EksclusterKfdV1Alpha2](&ClusterDeleter{}), + cluster.NewDeleterFactory[*ClusterDeleter, private.EksclusterKfdV1Alpha2](&ClusterDeleter{}), ) } diff --git a/internal/config/validate.go b/internal/config/validate.go index de9616986..585e9f9df 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -89,7 +89,7 @@ func Validate(path, repoPath string) error { return err } - schemaPath, err := distribution.GetSchemaPath(repoPath, miniConf) + schemaPath, err := distribution.GetPublicSchemaPath(repoPath, miniConf) if err != nil { return fmt.Errorf("error getting schema path: %w", err) } diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 5cdad811b..ef744568a 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -72,10 +72,6 @@ func (d *Downloader) DoDownload( ) (DownloadResult, error) { url := distroLocation - if err := d.validate.Struct(minimalConf); err != nil { - return DownloadResult{}, fmt.Errorf("invalid furyctl config: %w", err) - } - if distroLocation == "" { url = fmt.Sprintf(DefaultBaseURL, minimalConf.Spec.DistributionVersion) } diff --git a/internal/distribution/path.go b/internal/distribution/path.go index 98c136706..34748ff1e 100644 --- a/internal/distribution/path.go +++ b/internal/distribution/path.go @@ -24,8 +24,12 @@ func GetConfigTemplatePath(basePath string, conf config.Furyctl) (string, error) return getPath(basePath, conf, "%s-%s-%s.yaml.tpl", "templates/config") } -func GetSchemaPath(basePath string, conf config.Furyctl) (string, error) { - return getPath(basePath, conf, "%s-%s-%s.json", "schemas") +func GetPublicSchemaPath(basePath string, conf config.Furyctl) (string, error) { + return getPath(basePath, conf, "%s-%s-%s.json", "schemas/public") +} + +func GetPrivateSchemaPath(basePath string, conf config.Furyctl) (string, error) { + return getPath(basePath, conf, "%s-%s-%s.json", "schemas/private") } func GetDefaultsPath(basePath string) string { diff --git a/internal/distribution/path_test.go b/internal/distribution/path_test.go index 3e33b2a1e..6fc7ce82a 100644 --- a/internal/distribution/path_test.go +++ b/internal/distribution/path_test.go @@ -90,7 +90,21 @@ func TestGetTemplatePath(t *testing.T) { } } -func TestGetSchemaPath(t *testing.T) { +func TestGetSchemaPaths(t *testing.T) { + verifyPaths := func(t *testing.T, fname, got, want string, err, wantErr error) { + if err != nil { + if err.Error() != wantErr.Error() { + t.Errorf("distribution.%s() error = %v, wantErr %v", fname, err, wantErr) + } + + return + } + + if got != want { + t.Errorf("distribution.%s() = %v, want %v", fname, got, want) + } + } + tests := []struct { name string basePath string @@ -106,11 +120,12 @@ func TestGetSchemaPath(t *testing.T) { Kind: "EKSCluster", Spec: config.FuryctlSpec{}, }, - want: fmt.Sprintf("%s", filepath.Join( + want: filepath.Join( "testpath", "schemas", + "%s", "ekscluster-kfd-v1alpha2.json", - )), + ), wantErr: nil, }, { @@ -121,7 +136,11 @@ func TestGetSchemaPath(t *testing.T) { Kind: "EKSCluster", Spec: config.FuryctlSpec{}, }, - want: fmt.Sprintf("%s", filepath.Join("schemas", "ekscluster-kfd-v1alpha2.json")), + want: filepath.Join( + "schemas", + "%s", + "ekscluster-kfd-v1alpha2.json", + ), wantErr: nil, }, { @@ -149,18 +168,11 @@ func TestGetSchemaPath(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := distribution.GetSchemaPath(tt.basePath, tt.conf) - if err != nil { - if err.Error() != tt.wantErr.Error() { - t.Errorf("distribution.GetSchemaPath() error = %v, wantErr %v", err, tt.wantErr) - } - - return - } + egot, eerr := distribution.GetPublicSchemaPath(tt.basePath, tt.conf) + verifyPaths(t, "GetPublicSchemaPath", egot, fmt.Sprintf(tt.want, "public"), eerr, tt.wantErr) - if got != tt.want { - t.Errorf("distribution.GetSchemaPath() = %v, want %v", got, tt.want) - } + igot, ierr := distribution.GetPrivateSchemaPath(tt.basePath, tt.conf) + verifyPaths(t, "GetPrivateSchemaPath", igot, fmt.Sprintf(tt.want, "private"), ierr, tt.wantErr) }) } } diff --git a/test/data/e2e/create/cluster/data/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/e2e/create/cluster/data/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/test/data/e2e/create/config/default/data/expected-furyctl.yaml b/test/data/e2e/create/config/default/data/expected-furyctl.yaml index da2714bc0..1835270d4 100644 --- a/test/data/e2e/create/config/default/data/expected-furyctl.yaml +++ b/test/data/e2e/create/config/default/data/expected-furyctl.yaml @@ -2,250 +2,407 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +--- apiVersion: kfd.sighup.io/v1alpha2 kind: EKSCluster metadata: + # The name of the cluster, will be also used as a prefix for all the other resources created on AWS name: example spec: + # This value defines which KFD version will be installed and in consequence the Kubernetes version to use to create the cluster distributionVersion: v1.24.1 + # This sections defines where to store the terraform state files used by furyctl toolsConfiguration: terraform: state: s3: + # This value defines which bucket will be used to store all the states bucketName: example-bucket + # This value defines which folder will be used to store all the states inside the bucket keyPrefix: example-cluster/ + # This value defines in which region the bucket is region: eu-west-1 + # This value defines in which AWS region the cluster and all the related resources will be created region: eu-west-1 + # This map defines which will be the common tags that will be added to all the resources created on AWS tags: env: "example" k8s: "example2" + # This first section, infrastructure, defines the underlying network and eventual VPN bastions that will be provisioned. + # If you already have a VPC, you can remove this key from the configuration file infrastructure: + # This key defines the VPC that will be created in AWS vpc: network: + # This is the CIDR of the VPC cidr: 10.1.0.0/16 subnetsCidrs: + # These are the CIRDs for the private subnets, where the nodes, the pods, and the private load balancers will be created private: - 10.1.0.0/20 - 10.1.16.0/20 - 10.1.32.0/20 + # These are the CIDRs for the public subnets, where the public load balancers and the VPN servers will be created public: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 + # This section defines the creation of VPN bastions vpn: + # The number of instance to create, 0 to skip the creation instances: 2 + # The port used by the OpenVPN server port: 1194 + # The size of the AWS ec2 instance instanceType: t3.micro + # The size of the disk in GB diskSize: 50 + # The username of the account to create in the bastion's operating system operatorName: sighup + # The dhParamsBits size used for the creation of the .pem file that will be used in the dh openvpn server.conf file dhParamsBits: 2048 + # The CIDR that will be used to assign IP addresses to the VPN clients when connected vpnClientsSubnetCidr: 172.16.0.0/16 + # ssh access settings ssh: - publicKeys: - - "ssh-ed25519 XYZ" - - "{file://relative/path/to/ssh.pub}" + # Not yet supported + publicKeys: [] + # The github user name list that will be used to get the ssh public key that will be added as authorized key to the operatorName user githubUsersName: - johndoe + # The CIDR enabled in the security group that can access the bastions in SSH allowedFromCidrs: - 0.0.0.0/0 + # This section describes how the EKS cluster will be created kubernetes: - vpcId: vpc-0123456789abcdef0 - subnetIds: - - subnet-0123456789abcdef0 - - subnet-0123456789abcdef1 - - subnet-0123456789abcdef2 - apiServerEndpointAccess: - type: private - allowedCidrs: - - 10.0.0.0/16 + # This key contains the ssh public key that can connect to the nodes via SSH using the ec2-user user nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + # Either `launch_configurations`, `launch_templates` or `both`. For new clusters use `launch_templates`, for existing cluster you'll need to migrate from `launch_configurations` to `launch_templates` using `both` as interim. nodePoolsLaunchKind: "launch_templates" + # Optional Kubernetes Cluster log retention in days. Defaults to 90 days. + # logRetentionDays: 90 + # This array contains the definition of the nodepools in the cluster nodePools: - - name: worker - ami: - id: ami-0123456789abcdef0 - owner: "123456789012" + # This is the name of the nodepool + - name: infra + # This optional map defines a different AMI to use for the instances + #ami: + # id: ami-0123456789abcdef0 + # owner: "123456789012" + # This map defines the max and min number of nodes in the nodepool autoscaling group size: min: 1 max: 3 - subnetIds: - - subnet-0123456789abcdef0 - - subnet-0123456789abcdef1 - - subnet-0123456789abcdef2 + # This map defines the characteristics of the instance that will be used in the node pool instance: - type: t3.micro + # The instance type + type: t3.xlarge + # If the instance is a spot instance spot: false + # The instance disk size in GB volumeSize: 50 - attachedTargetGroups: - - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-external-nginx/0123456789abcdee - - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-internal-nginx/0123456789abcdef + # This optional array defines additional target groups to attach to the instances in the node pool + #attachedTargetGroups: + # - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-external-nginx/0123456789abcdee + # - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-internal-nginx/0123456789abcdef + # Kubernetes labels that will be added to the nodes labels: - nodepool: worker - node.kubernetes.io/role: worker + nodepool: infra + node.kubernetes.io/role: infra + # Kubernetes taints that will be added to the nodes taints: - - node.kubernetes.io/role=worker:NoSchedule + - node.kubernetes.io/role=infra:NoSchedule + # AWS tags that will be added to the ASG and EC2 instances, the example shows the labels needed by cluster autoscaler tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 - tags: {} - awsAuth: - additionalAccounts: - - "777777777777" - - "88888888888" - users: - - username: "johndoe" - groups: - - system:masters - userarn: "arn:aws:iam::123456789012:user/johndoe" - roles: - - username: "example" - groups: - - example:masters - rolearn: "arn:aws:iam::123456789012:role/k8s-example-role" + # Optional additional firewall rules that will be attached to the nodes + #additionalFirewallRules: + # # The name of the rule + # - name: traffic_80_from_172_31_0_0_16 + # # The type of the rule, can be ingress or egress + # type: ingress + # # The CIDR blocks for the FW rule. At the moment the first item of the list will be used, others will be ignored. See https://github.com/sighupio/fury-eks-installer/issues/46 + # cidrBlocks: + # - 172.31.0.0/16 + # # The protocol + # protocol: TCP + # # The ports range + # ports: + # from: 80 + # to: 80 + # # Additional AWS tags + # tags: {} + # aws-auth configmap definition, see https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html for more informations + awsAuth: {} + # additionalAccounts: + # - "777777777777" + # - "88888888888" + # users: + # - username: "johndoe" + # groups: + # - system:masters + # userarn: "arn:aws:iam::123456789012:user/johndoe" + # roles: + # - username: "example" + # groups: + # - example:masters + # rolearn: "arn:aws:iam::123456789012:role/k8s-example-role" + # Optional. Use when spec.infrastructure is left empty and the VPC is not managed by furyctl + vpcId: "vpc-123456780" + # This section describes how the KFD distribution will be installed distribution: + # This common configuration will be applied to all the packages that will be installed in the cluster common: + # The node selector to use to place the pods for all the KFD packages nodeSelector: node.kubernetes.io/role: infra + # The tolerations that will be added to the pods for all the KFD packages tolerations: - effect: NoSchedule key: node.kubernetes.io/role value: infra + # This section contains all the configurations for all the KFD core modules modules: + # This section contains all the configurations for the ingress module ingress: - overrides: - nodeSelector: null - tolerations: null - ingresses: - forecastle: - disableAuth: false - host: "" - ingressClass: "" - baseDomain: example.dev + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector settings. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations settings. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # forecastle: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is directory.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # the base domain used for all the KFD ingresses, if in the nginx dual configuration, it should be the same as the .spec.distribution.modules.ingress.dns.private.name zone + baseDomain: internal.example.dev + # configurations for the nginx ingress controller package nginx: - type: single + # type defines if the nginx should be configured as single or dual (internal + external) + type: dual + # the tls section defines how the tls for the ingresses should be managed tls: + # provider can be certManager, secret provider: certManager + # if provider is set as secret, this key will be used to create the certificate in the cluster secret: + # the certificate file content or you can use the file notation to get the content from a file cert: "{file://relative/path/to/ssl.crt}" + # the key file, a file notation can be used to get the content from a file key: "{file://relative/path/to/ssl.key}" + # the ca file, a file notation can be used to get the content from a file ca: "{file://relative/path/to/ssl.ca}" + # configuration for the cert-manager package certManager: + # the configuration for the clusterIssuer that will be created clusterIssuer: + # the name of the clusterIssuer name: letsencrypt-fury + # the email used during issuing procedures + email: example@sighup.io + # the type of the clusterIssuer, can be http01 or dns01, if dns01, the route53 integration will be used type: http01 + # DNS definition, used in conjunction with externalDNS package to automate DNS management and certificates emission dns: + # the public DNS zone definition public: + # the name of the zone name: "example.dev" + # manage if we need to create the zone, or if it already exists and we only need to adopt/use it create: false + # the private DNS zone definition, that will be attached to the VPC private: + # the name of the zone name: "internal.example.dev" - vpcId: "vpc-0123456789abcdef0" + # defines if we need to create the zone, or if it already exists and we only need to adopt/use it + create: false + # This field is ignored, but needed. TBD better validation + vpcId: "dummyvalue" + # This section contains all the configurations for the logging module logging: - overrides: - nodeSelector: null - tolerations: null - ingresses: - opensearch-dashboards: - disableAuth: false - host: "" - ingressClass: "" - cerebro: - disableAuth: false - host: "" - ingressClass: "" + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # opensearchDashboards: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is opensearch-dashboards.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # cerebro: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is cerebro.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # configurations for the opensearch package opensearch: + # the type of opensearch to install, can be single or triple type: single - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" + ## optional settings to override requests and limits + #resources: + # requests: + # cpu: "" + # memory: "" + # limits: + # cpu: "" + # memory: "" + # the PVC size used by opensearch, for each pod storageSize: "150Gi" + # This section contains all the configurations for the monitoring module monitoring: - overrides: - nodeSelector: null - tolerations: null - ingresses: - prometheus: - disableAuth: false - host: "" - ingressClass: "" - alertmanager: - disableAuth: false - host: "" - ingressClass: "" - grafana: - disableAuth: false - host: "" - ingressClass: "" - goldpinger: - disableAuth: false - host: "" - ingressClass: "" - prometheus: - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # prometheus: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is prometheus.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # alertmanager: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is alertmanager.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # grafana: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is grafana.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # configurations for the prometheus package + #prometheus: + # # optional settings to override requests and limits + # resources: + # requests: + # cpu: "" + # memory: "" + # limits: + # cpu: "" + # memory: "" + # configurations for the alertmanager package + alertmanager: + # The webhook url to send deadman switch monitoring, for example to use with healthchecks.io + deadManSwitchWebhookUrl: "" + # The slack webhook url to send alerts + slackWebhookUrl: https://slack.com + # This section contains all the configurations for the policy (opa) module policy: - overrides: - nodeSelector: null - tolerations: null - ingresses: - gpm: - disableAuth: false - host: "" - ingressClass: "" + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # gpm: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is gpm.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # configurations for the gatekeeper package gatekeeper: + # This parameter adds namespaces to Gatekeeper's exemption list, so it will not enforce the constraints on them. additionalExcludedNamespaces: [] + # This section contains all the configurations for the Disaster Recovery module dr: - overrides: - nodeSelector: null - tolerations: null + # Configurations for the velero package + velero: + # Velero configurations for EKS cluster + eks: + # The S3 bucket that will be created to store the backups + bucketName: example-velero + # This field is ignored, but needed. TBD better validation + iamRoleArn: arn:aws:iam::123456789012:role/dummy-value + # The region where the bucket will be created (can be different from the overall region defined in .spec.region) + region: eu-west-1 + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # This section contains all the configurations for the auth module auth: - overrides: - nodeSelector: null - ingresses: - pomerium: - host: "" - ingressClass: "" - dex: - host: "" - ingressClass: "" - tolerations: null + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # ingresses: + # pomerium: + # # the host can be overridden, by default is pomerium.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # dex: + # # the host can be overridden, by default is login.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" provider: + # The authentication type used for the infrastructure ingresses (all the ingress for the distribution) can be none, basicAuth, sso type: none + # configuration for the basicAuth if .spec.distribution.modules.auth.provider.type is basicAuth basicAuth: + # The username username: admin + # The password password: "{env://KFD_BASIC_AUTH_PASSWORD}" - pomerium: - secrets: - COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" - IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" - SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" - dex: - connectors: - - type: github - id: github - name: GitHub - config: - clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" - clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" - redirectURI: https://login.example.dev/callback - loadAllGroups: false - teamNameField: slug - useLoginAsID: false + # The base domain used for all the auth ingresses, if in the nginx dual configuration, it should be the same as the .spec.distribution.modules.ingress.dns.public.name zone + baseDomain: example.dev + # Configuration for the pomerium package, used only if .spec.distribution.modules.auth.provider.type is sso + #pomerium: + # # Additional policy configuration + # policy: | + # - from: https://myapp.example.dev + # to: http://myapp.svc.cluster.local:8000 + # cors_allow_preflight: true + # timeout: 30s + # # Secrets configurations for pomerium and dex (pomerium connect to dex proxy for the SSO process) + # secrets: + # COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" + # IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" + # SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" + ## Configuration for the pomerium package, used only if .spec.distribution.modules.auth.provider.type is sso + #dex: + # # Dex connectors configuration + # connectors: + # - type: github + # id: github + # name: GitHub + # config: + # clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + # clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" + # redirectURI: https://login.example.dev/callback + # loadAllGroups: false + # teamNameField: slug + # useLoginAsID: false diff --git a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl index 8c7c13ee6..44998f975 100644 --- a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl +++ b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl @@ -2,250 +2,407 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +--- apiVersion: kfd.sighup.io/v1alpha2 kind: EKSCluster metadata: + # The name of the cluster, will be also used as a prefix for all the other resources created on AWS name: {{.Name}} spec: + # This value defines which KFD version will be installed and in consequence the Kubernetes version to use to create the cluster distributionVersion: {{.DistributionVersion}} + # This sections defines where to store the terraform state files used by furyctl toolsConfiguration: terraform: state: s3: + # This value defines which bucket will be used to store all the states bucketName: example-bucket + # This value defines which folder will be used to store all the states inside the bucket keyPrefix: example-cluster/ + # This value defines in which region the bucket is region: eu-west-1 + # This value defines in which AWS region the cluster and all the related resources will be created region: eu-west-1 + # This map defines which will be the common tags that will be added to all the resources created on AWS tags: env: "example" k8s: "example2" + # This first section, infrastructure, defines the underlying network and eventual VPN bastions that will be provisioned. + # If you already have a VPC, you can remove this key from the configuration file infrastructure: + # This key defines the VPC that will be created in AWS vpc: network: + # This is the CIDR of the VPC cidr: 10.1.0.0/16 subnetsCidrs: + # These are the CIRDs for the private subnets, where the nodes, the pods, and the private load balancers will be created private: - 10.1.0.0/20 - 10.1.16.0/20 - 10.1.32.0/20 + # These are the CIDRs for the public subnets, where the public load balancers and the VPN servers will be created public: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 + # This section defines the creation of VPN bastions vpn: + # The number of instance to create, 0 to skip the creation instances: 2 + # The port used by the OpenVPN server port: 1194 + # The size of the AWS ec2 instance instanceType: t3.micro + # The size of the disk in GB diskSize: 50 + # The username of the account to create in the bastion's operating system operatorName: sighup + # The dhParamsBits size used for the creation of the .pem file that will be used in the dh openvpn server.conf file dhParamsBits: 2048 + # The CIDR that will be used to assign IP addresses to the VPN clients when connected vpnClientsSubnetCidr: 172.16.0.0/16 + # ssh access settings ssh: - publicKeys: - - "ssh-ed25519 XYZ" - - "{file://relative/path/to/ssh.pub}" + # Not yet supported + publicKeys: [] + # The github user name list that will be used to get the ssh public key that will be added as authorized key to the operatorName user githubUsersName: - johndoe + # The CIDR enabled in the security group that can access the bastions in SSH allowedFromCidrs: - 0.0.0.0/0 + # This section describes how the EKS cluster will be created kubernetes: - vpcId: vpc-0123456789abcdef0 - subnetIds: - - subnet-0123456789abcdef0 - - subnet-0123456789abcdef1 - - subnet-0123456789abcdef2 - apiServerEndpointAccess: - type: private - allowedCidrs: - - 10.0.0.0/16 + # This key contains the ssh public key that can connect to the nodes via SSH using the ec2-user user nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" + # Either `launch_configurations`, `launch_templates` or `both`. For new clusters use `launch_templates`, for existing cluster you'll need to migrate from `launch_configurations` to `launch_templates` using `both` as interim. nodePoolsLaunchKind: "launch_templates" + # Optional Kubernetes Cluster log retention in days. Defaults to 90 days. + # logRetentionDays: 90 + # This array contains the definition of the nodepools in the cluster nodePools: - - name: worker - ami: - id: ami-0123456789abcdef0 - owner: "123456789012" + # This is the name of the nodepool + - name: infra + # This optional map defines a different AMI to use for the instances + #ami: + # id: ami-0123456789abcdef0 + # owner: "123456789012" + # This map defines the max and min number of nodes in the nodepool autoscaling group size: min: 1 max: 3 - subnetIds: - - subnet-0123456789abcdef0 - - subnet-0123456789abcdef1 - - subnet-0123456789abcdef2 + # This map defines the characteristics of the instance that will be used in the node pool instance: - type: t3.micro + # The instance type + type: t3.xlarge + # If the instance is a spot instance spot: false + # The instance disk size in GB volumeSize: 50 - attachedTargetGroups: - - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-external-nginx/0123456789abcdee - - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-internal-nginx/0123456789abcdef + # This optional array defines additional target groups to attach to the instances in the node pool + #attachedTargetGroups: + # - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-external-nginx/0123456789abcdee + # - arn:aws:elasticloadbalancing:eu-west-1:123456789012:targetgroup/example-internal-nginx/0123456789abcdef + # Kubernetes labels that will be added to the nodes labels: - nodepool: worker - node.kubernetes.io/role: worker + nodepool: infra + node.kubernetes.io/role: infra + # Kubernetes taints that will be added to the nodes taints: - - node.kubernetes.io/role=worker:NoSchedule + - node.kubernetes.io/role=infra:NoSchedule + # AWS tags that will be added to the ASG and EC2 instances, the example shows the labels needed by cluster autoscaler tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 - tags: {} - awsAuth: - additionalAccounts: - - "777777777777" - - "88888888888" - users: - - username: "johndoe" - groups: - - system:masters - userarn: "arn:aws:iam::123456789012:user/johndoe" - roles: - - username: "example" - groups: - - example:masters - rolearn: "arn:aws:iam::123456789012:role/k8s-example-role" + # Optional additional firewall rules that will be attached to the nodes + #additionalFirewallRules: + # # The name of the rule + # - name: traffic_80_from_172_31_0_0_16 + # # The type of the rule, can be ingress or egress + # type: ingress + # # The CIDR blocks for the FW rule. At the moment the first item of the list will be used, others will be ignored. See https://github.com/sighupio/fury-eks-installer/issues/46 + # cidrBlocks: + # - 172.31.0.0/16 + # # The protocol + # protocol: TCP + # # The ports range + # ports: + # from: 80 + # to: 80 + # # Additional AWS tags + # tags: {} + # aws-auth configmap definition, see https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html for more informations + awsAuth: {} + # additionalAccounts: + # - "777777777777" + # - "88888888888" + # users: + # - username: "johndoe" + # groups: + # - system:masters + # userarn: "arn:aws:iam::123456789012:user/johndoe" + # roles: + # - username: "example" + # groups: + # - example:masters + # rolearn: "arn:aws:iam::123456789012:role/k8s-example-role" + # Optional. Use when spec.infrastructure is left empty and the VPC is not managed by furyctl + vpcId: "vpc-123456780" + # This section describes how the KFD distribution will be installed distribution: + # This common configuration will be applied to all the packages that will be installed in the cluster common: + # The node selector to use to place the pods for all the KFD packages nodeSelector: node.kubernetes.io/role: infra + # The tolerations that will be added to the pods for all the KFD packages tolerations: - effect: NoSchedule key: node.kubernetes.io/role value: infra + # This section contains all the configurations for all the KFD core modules modules: + # This section contains all the configurations for the ingress module ingress: - overrides: - nodeSelector: null - tolerations: null - ingresses: - forecastle: - disableAuth: false - host: "" - ingressClass: "" - baseDomain: example.dev + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector settings. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations settings. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # forecastle: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is directory.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # the base domain used for all the KFD ingresses, if in the nginx dual configuration, it should be the same as the .spec.distribution.modules.ingress.dns.private.name zone + baseDomain: internal.example.dev + # configurations for the nginx ingress controller package nginx: - type: single + # type defines if the nginx should be configured as single or dual (internal + external) + type: dual + # the tls section defines how the tls for the ingresses should be managed tls: + # provider can be certManager, secret provider: certManager + # if provider is set as secret, this key will be used to create the certificate in the cluster secret: + # the certificate file content or you can use the file notation to get the content from a file cert: "{file://relative/path/to/ssl.crt}" + # the key file, a file notation can be used to get the content from a file key: "{file://relative/path/to/ssl.key}" + # the ca file, a file notation can be used to get the content from a file ca: "{file://relative/path/to/ssl.ca}" + # configuration for the cert-manager package certManager: + # the configuration for the clusterIssuer that will be created clusterIssuer: + # the name of the clusterIssuer name: letsencrypt-fury + # the email used during issuing procedures + email: example@sighup.io + # the type of the clusterIssuer, can be http01 or dns01, if dns01, the route53 integration will be used type: http01 + # DNS definition, used in conjunction with externalDNS package to automate DNS management and certificates emission dns: + # the public DNS zone definition public: + # the name of the zone name: "example.dev" + # manage if we need to create the zone, or if it already exists and we only need to adopt/use it create: false + # the private DNS zone definition, that will be attached to the VPC private: + # the name of the zone name: "internal.example.dev" - vpcId: "vpc-0123456789abcdef0" + # defines if we need to create the zone, or if it already exists and we only need to adopt/use it + create: false + # This field is ignored, but needed. TBD better validation + vpcId: "dummyvalue" + # This section contains all the configurations for the logging module logging: - overrides: - nodeSelector: null - tolerations: null - ingresses: - opensearch-dashboards: - disableAuth: false - host: "" - ingressClass: "" - cerebro: - disableAuth: false - host: "" - ingressClass: "" + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # opensearchDashboards: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is opensearch-dashboards.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # cerebro: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is cerebro.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # configurations for the opensearch package opensearch: + # the type of opensearch to install, can be single or triple type: single - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" + ## optional settings to override requests and limits + #resources: + # requests: + # cpu: "" + # memory: "" + # limits: + # cpu: "" + # memory: "" + # the PVC size used by opensearch, for each pod storageSize: "150Gi" + # This section contains all the configurations for the monitoring module monitoring: - overrides: - nodeSelector: null - tolerations: null - ingresses: - prometheus: - disableAuth: false - host: "" - ingressClass: "" - alertmanager: - disableAuth: false - host: "" - ingressClass: "" - grafana: - disableAuth: false - host: "" - ingressClass: "" - goldpinger: - disableAuth: false - host: "" - ingressClass: "" - prometheus: - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # prometheus: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is prometheus.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # alertmanager: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is alertmanager.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # grafana: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is grafana.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # configurations for the prometheus package + #prometheus: + # # optional settings to override requests and limits + # resources: + # requests: + # cpu: "" + # memory: "" + # limits: + # cpu: "" + # memory: "" + # configurations for the alertmanager package + alertmanager: + # The webhook url to send deadman switch monitoring, for example to use with healthchecks.io + deadManSwitchWebhookUrl: "" + # The slack webhook url to send alerts + slackWebhookUrl: https://slack.com + # This section contains all the configurations for the policy (opa) module policy: - overrides: - nodeSelector: null - tolerations: null - ingresses: - gpm: - disableAuth: false - host: "" - ingressClass: "" + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # # This key is used to override some parameters on the ingresses managed by this module + # ingresses: + # gpm: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is gpm.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # configurations for the gatekeeper package gatekeeper: + # This parameter adds namespaces to Gatekeeper's exemption list, so it will not enforce the constraints on them. additionalExcludedNamespaces: [] + # This section contains all the configurations for the Disaster Recovery module dr: - overrides: - nodeSelector: null - tolerations: null + # Configurations for the velero package + velero: + # Velero configurations for EKS cluster + eks: + # The S3 bucket that will be created to store the backups + bucketName: example-velero + # This field is ignored, but needed. TBD better validation + iamRoleArn: arn:aws:iam::123456789012:role/dummy-value + # The region where the bucket will be created (can be different from the overall region defined in .spec.region) + region: eu-west-1 + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # This section contains all the configurations for the auth module auth: - overrides: - nodeSelector: null - ingresses: - pomerium: - host: "" - ingressClass: "" - dex: - host: "" - ingressClass: "" - tolerations: null + # This optional key is used to override automatic parameters + #overrides: + # # This key is used to override the spec.distribution.common.nodeSelector setting. Set to a custom value or use an empty object {} to not add the common node selector. + # nodeSelector: null + # # This key is used to override the spec.distribution.common.tolerations setting. Set to a custom value or use an empty object {} to not add the common tolerations. + # tolerations: null + # ingresses: + # pomerium: + # # the host can be overridden, by default is pomerium.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # dex: + # # the host can be overridden, by default is login.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" provider: + # The authentication type used for the infrastructure ingresses (all the ingress for the distribution) can be none, basicAuth, sso type: none + # configuration for the basicAuth if .spec.distribution.modules.auth.provider.type is basicAuth basicAuth: + # The username username: admin + # The password password: "{env://KFD_BASIC_AUTH_PASSWORD}" - pomerium: - secrets: - COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" - IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" - SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" - dex: - connectors: - - type: github - id: github - name: GitHub - config: - clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" - clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" - redirectURI: https://login.example.dev/callback - loadAllGroups: false - teamNameField: slug - useLoginAsID: false + # The base domain used for all the auth ingresses, if in the nginx dual configuration, it should be the same as the .spec.distribution.modules.ingress.dns.public.name zone + baseDomain: example.dev + # Configuration for the pomerium package, used only if .spec.distribution.modules.auth.provider.type is sso + #pomerium: + # # Additional policy configuration + # policy: | + # - from: https://myapp.example.dev + # to: http://myapp.svc.cluster.local:8000 + # cors_allow_preflight: true + # timeout: 30s + # # Secrets configurations for pomerium and dex (pomerium connect to dex proxy for the SSO process) + # secrets: + # COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" + # IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" + # SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" + ## Configuration for the pomerium package, used only if .spec.distribution.modules.auth.provider.type is sso + #dex: + # # Dex connectors configuration + # connectors: + # - type: github + # id: github + # name: GitHub + # config: + # clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + # clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" + # redirectURI: https://login.example.dev/callback + # loadAllGroups: false + # teamNameField: slug + # useLoginAsID: false diff --git a/test/data/e2e/download/dependencies/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/download/dependencies/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/e2e/download/dependencies/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/e2e/download/dependencies/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/e2e/validate/config/correct/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/e2e/validate/config/wrong/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/test/data/expensive/common/data/schemas/ekscluster-kfd-v1alpha2.json b/test/data/expensive/common/data/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/expensive/common/data/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/expensive/common/data/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/test/data/integration/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json b/test/data/integration/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/integration/v1.24.1/distro/schemas/ekscluster-kfd-v1alpha2.json rename to test/data/integration/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json From 8d6120ae791a52c2181658ad4521567328a26827 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:53:48 +0100 Subject: [PATCH 134/383] fix: now kubeconfig path is absolute (#306) --- cmd/create/cluster.go | 18 ++++++++++-------- cmd/delete/cluster.go | 25 +++++++++++++++++++++---- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index abe88e90d..072ab33e0 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -76,7 +76,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Check if kubeconfig is needed. if flags.Phase == cluster.OperationPhaseDistribution || flags.SkipPhase == cluster.OperationPhaseKubernetes { - if flags.Kubeconfig == "" { + if kubeconfigPath == "" { kubeconfigFromEnv := os.Getenv("KUBECONFIG") if kubeconfigFromEnv == "" { @@ -88,14 +88,16 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { logrus.Warnf("Missing --kubeconfig flag, falling back to KUBECONFIG from environment: %s", kubeconfigFromEnv) } + kubeAbsPath, err := filepath.Abs(kubeconfigPath) + if err != nil { + return fmt.Errorf("error while getting absolute path of kubeconfig: %w", err) + } + + kubeconfigPath = kubeAbsPath + // Check the kubeconfig file exists. if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) { - kubeAbsPath, err := filepath.Abs(kubeconfigPath) - if err != nil { - return fmt.Errorf("error while getting absolute path of kubeconfig: %w", err) - } - - return fmt.Errorf("%w in %s", ErrKubeconfigNotFound, kubeAbsPath) + return fmt.Errorf("%w in %s", ErrKubeconfigNotFound, kubeconfigPath) } } @@ -180,7 +182,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { WorkDir: basePath, DistroPath: res.RepoPath, BinPath: flags.BinPath, - Kubeconfig: flags.Kubeconfig, + Kubeconfig: kubeconfigPath, } // Set debug mode. diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index c683ede7c..cf97c89b9 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -26,8 +26,9 @@ import ( ) var ( - ErrParsingFlag = errors.New("error while parsing flag") - ErrKubeconfigReq = errors.New("when running distribution phase, either the KUBECONFIG environment variable or the --kubeconfig flag should be set") + ErrParsingFlag = errors.New("error while parsing flag") + ErrKubeconfigReq = errors.New("when running distribution phase, either the KUBECONFIG environment variable or the --kubeconfig flag should be set") + ErrKubeconfigNotFound = errors.New("kubeconfig file not found") ) type ClusterCmdFlags struct { @@ -64,17 +65,33 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while getting user home directory: %w", err) } + kubeconfigPath := flags.Kubeconfig + // Check if kubeconfig is needed. if flags.Phase == cluster.OperationPhaseDistribution || flags.Phase == cluster.OperationPhaseAll { - if flags.Kubeconfig == "" { + if kubeconfigPath == "" { kubeconfigFromEnv := os.Getenv("KUBECONFIG") if kubeconfigFromEnv == "" { return ErrKubeconfigReq } + kubeconfigPath = kubeconfigFromEnv + logrus.Warnf("Missing --kubeconfig flag, falling back to KUBECONFIG from environment: %s", kubeconfigFromEnv) } + + kubeAbsPath, err := filepath.Abs(kubeconfigPath) + if err != nil { + return fmt.Errorf("error while getting absolute path of kubeconfig: %w", err) + } + + kubeconfigPath = kubeAbsPath + + // Check the kubeconfig file exists. + if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) { + return fmt.Errorf("%w in %s", ErrKubeconfigNotFound, kubeconfigPath) + } } if flags.BinPath == "" { @@ -119,7 +136,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { flags.Phase, basePath, flags.BinPath, - flags.Kubeconfig, + kubeconfigPath, flags.DryRun, ) if err != nil { From 28c15445dcb689a2012050e7b6b2857f3906a20f Mon Sep 17 00:00:00 2001 From: omissis Date: Fri, 24 Feb 2023 10:44:20 +0100 Subject: [PATCH 135/383] feat: introduce extra validation beyond json schema --- internal/config/validate.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/internal/config/validate.go b/internal/config/validate.go index 585e9f9df..4bed4f3c9 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -11,6 +11,7 @@ import ( "path/filepath" "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/schema/santhosh" @@ -106,7 +107,32 @@ func Validate(path, repoPath string) error { err = schema.Validate(conf) if err != nil { - return fmt.Errorf("error while validating: %w", err) + return fmt.Errorf("error while validating against schema: %w", err) + } + + err = validateSchemaExtraRules(path) + if err != nil { + return fmt.Errorf("error while validating against schema rules: %w", err) + } + + return nil +} + +func validateSchemaExtraRules(confPath string) error { + furyctlConf, err := yamlx.FromFileV3[private.EksclusterKfdV1Alpha2](confPath) + if err != nil { + return err + } + + for i, nodePool := range furyctlConf.Spec.Kubernetes.NodePools { + if nodePool.Size.Max < nodePool.Size.Min { + return fmt.Errorf( + "spec.kubernetes.nodePools[%d].size's max(%d) must be greater than or equal to min(%d)", + i, + nodePool.Size.Max, + nodePool.Size.Min, + ) + } } return nil From cacdc520224fe7f24566137bbcf5c61fa6c79721 Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 27 Feb 2023 17:03:32 +0100 Subject: [PATCH 136/383] chore: move extra schema validation to a dedicated adapter --- internal/apis/kfd/v1alpha2/eks/schema.go | 33 ++++++++++ internal/apis/kfd/v1alpha2/eks/schema_test.go | 63 +++++++++++++++++++ .../kfd/v1alpha2/eks/test/schema/invalid.yaml | 1 + .../eks/test/schema/min_equal_to_max.yaml | 6 ++ .../eks/test/schema/min_greater_than_max.yaml | 6 ++ .../eks/test/schema/min_lesser_than_max.yaml | 6 ++ internal/apis/schema.go | 23 +++++++ internal/config/validate.go | 31 ++------- internal/distribution/download.go | 2 +- 9 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 internal/apis/kfd/v1alpha2/eks/schema.go create mode 100644 internal/apis/kfd/v1alpha2/eks/schema_test.go create mode 100644 internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml create mode 100644 internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml create mode 100644 internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml create mode 100644 internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml create mode 100644 internal/apis/schema.go diff --git a/internal/apis/kfd/v1alpha2/eks/schema.go b/internal/apis/kfd/v1alpha2/eks/schema.go new file mode 100644 index 000000000..c9b494a2a --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/schema.go @@ -0,0 +1,33 @@ +package eks + +import ( + "fmt" + + "github.com/sighupio/fury-distribution/pkg/schema/private" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +var ErrInvalidNodePoolSize = fmt.Errorf("invalid node pool size") + +type ExtraSchemaValidator struct{} + +func (*ExtraSchemaValidator) Validate(confPath string) error { + furyctlConf, err := yamlx.FromFileV3[private.EksclusterKfdV1Alpha2](confPath) + if err != nil { + return err + } + + for i, nodePool := range furyctlConf.Spec.Kubernetes.NodePools { + if nodePool.Size.Max < nodePool.Size.Min { + return fmt.Errorf( + "%w: element %d's max size(%d) must be greater than or equal to its min(%d)", + ErrInvalidNodePoolSize, + i, + nodePool.Size.Max, + nodePool.Size.Min, + ) + } + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/schema_test.go b/internal/apis/kfd/v1alpha2/eks/schema_test.go new file mode 100644 index 000000000..3de28f6b9 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/schema_test.go @@ -0,0 +1,63 @@ +package eks_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" +) + +func Test_ExtraSchemaValidator_Validate(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + confPath string + wantErr bool + wantErrMsg string + }{ + { + desc: "min size is lesser than max size", + confPath: "test/schema/min_lesser_than_max.yaml", + }, + { + desc: "min size is equal to max size", + confPath: "test/schema/min_equal_to_max.yaml", + }, + { + desc: "min size is greater than max size", + confPath: "test/schema/min_greater_than_max.yaml", + wantErr: true, + wantErrMsg: "invalid node pool size: element 0's max size(1) must be greater than or equal to its min(2)", + }, + { + desc: "furyctl config is invalid", + confPath: "test/schema/invalid.yaml", + wantErr: true, + wantErrMsg: "error while unmarshalling file from test/schema/invalid.yaml" + + " :yaml: line 1: did not find expected ',' or '}'", + }, + } + + esv := &eks.ExtraSchemaValidator{} + + for _, tC := range testCases { + tC := tC + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + err := esv.Validate(tC.confPath) + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected nil, got error: %v", err) + } + + if tC.wantErr && err != nil && err.Error() != tC.wantErrMsg { + t.Errorf("expected error message '%s', got '%s'", tC.wantErrMsg, err.Error()) + } + }) + } +} diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml new file mode 100644 index 000000000..fd34e5e33 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml @@ -0,0 +1 @@ +{ broken*yaml diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml new file mode 100644 index 000000000..230970a2b --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml @@ -0,0 +1,6 @@ +spec: + kubernetes: + nodePools: + - size: + min: 2 + max: 2 diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml new file mode 100644 index 000000000..6463f794d --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml @@ -0,0 +1,6 @@ +spec: + kubernetes: + nodePools: + - size: + min: 2 + max: 1 diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml new file mode 100644 index 000000000..027aa5a16 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml @@ -0,0 +1,6 @@ +spec: + kubernetes: + nodePools: + - size: + min: 1 + max: 2 diff --git a/internal/apis/schema.go b/internal/apis/schema.go new file mode 100644 index 000000000..bd193d2a8 --- /dev/null +++ b/internal/apis/schema.go @@ -0,0 +1,23 @@ +package apis + +import "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" + +type ExtraSchemaValidator interface { + Validate(confPath string) error +} + +func NewExtraSchemaValidatorFactory(apiVersion, kind string) ExtraSchemaValidator { + switch apiVersion { + case "kfd.sighup.io/v1alpha2": + switch kind { + case "EKSCluster": + return &eks.ExtraSchemaValidator{} + + default: + return nil + } + + default: + return nil + } +} diff --git a/internal/config/validate.go b/internal/config/validate.go index 4bed4f3c9..fc1238683 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -11,8 +11,8 @@ import ( "path/filepath" "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/apis" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/schema/santhosh" iox "github.com/sighupio/furyctl/internal/x/io" @@ -105,34 +105,13 @@ func Validate(path, repoPath string) error { return err } - err = schema.Validate(conf) - if err != nil { + if err = schema.Validate(conf); err != nil { return fmt.Errorf("error while validating against schema: %w", err) } - err = validateSchemaExtraRules(path) - if err != nil { - return fmt.Errorf("error while validating against schema rules: %w", err) - } - - return nil -} - -func validateSchemaExtraRules(confPath string) error { - furyctlConf, err := yamlx.FromFileV3[private.EksclusterKfdV1Alpha2](confPath) - if err != nil { - return err - } - - for i, nodePool := range furyctlConf.Spec.Kubernetes.NodePools { - if nodePool.Size.Max < nodePool.Size.Min { - return fmt.Errorf( - "spec.kubernetes.nodePools[%d].size's max(%d) must be greater than or equal to min(%d)", - i, - nodePool.Size.Max, - nodePool.Size.Min, - ) - } + esv := apis.NewExtraSchemaValidatorFactory(miniConf.APIVersion, miniConf.Kind) + if err = esv.Validate(path); err != nil { + return fmt.Errorf("error while validating against extra schema rules: %w", err) } return nil diff --git a/internal/distribution/download.go b/internal/distribution/download.go index ef744568a..057c690bc 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -60,7 +60,7 @@ func (d *Downloader) Download( ) (DownloadResult, error) { minimalConf, err := yamlx.FromFileV3[config.Furyctl](furyctlConfPath) if err != nil { - return DownloadResult{}, fmt.Errorf("%w: %s", ErrYamlUnmarshalFile, err) + return DownloadResult{}, fmt.Errorf("%w: %v", ErrYamlUnmarshalFile, err) } return d.DoDownload(distroLocation, minimalConf) From 8bdc500ad64c543aeed4007d565927fa3177851b Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 27 Feb 2023 17:10:55 +0100 Subject: [PATCH 137/383] chore: add missing license banners --- internal/apis/kfd/v1alpha2/eks/schema.go | 4 ++++ internal/apis/kfd/v1alpha2/eks/schema_test.go | 4 ++++ internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml | 4 ++++ .../apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml | 4 ++++ .../kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml | 4 ++++ .../kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml | 4 ++++ internal/apis/schema.go | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/schema.go b/internal/apis/kfd/v1alpha2/eks/schema.go index c9b494a2a..74789958c 100644 --- a/internal/apis/kfd/v1alpha2/eks/schema.go +++ b/internal/apis/kfd/v1alpha2/eks/schema.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package eks import ( diff --git a/internal/apis/kfd/v1alpha2/eks/schema_test.go b/internal/apis/kfd/v1alpha2/eks/schema_test.go index 3de28f6b9..6b2b6ce7e 100644 --- a/internal/apis/kfd/v1alpha2/eks/schema_test.go +++ b/internal/apis/kfd/v1alpha2/eks/schema_test.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package eks_test import ( diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml index fd34e5e33..20667f3bd 100644 --- a/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/invalid.yaml @@ -1 +1,5 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + { broken*yaml diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml index 230970a2b..5b87ad6fa 100644 --- a/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/min_equal_to_max.yaml @@ -1,3 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + spec: kubernetes: nodePools: diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml index 6463f794d..3471a70cf 100644 --- a/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/min_greater_than_max.yaml @@ -1,3 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + spec: kubernetes: nodePools: diff --git a/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml b/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml index 027aa5a16..8d67285ce 100644 --- a/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml +++ b/internal/apis/kfd/v1alpha2/eks/test/schema/min_lesser_than_max.yaml @@ -1,3 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + spec: kubernetes: nodePools: diff --git a/internal/apis/schema.go b/internal/apis/schema.go index bd193d2a8..ed8e523d4 100644 --- a/internal/apis/schema.go +++ b/internal/apis/schema.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package apis import "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" From d303139e58fc63f3f5e0a6fee8c5eebe35a6bd0e Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 27 Feb 2023 17:18:10 +0100 Subject: [PATCH 138/383] fix: amend case and add missing build tag in schema test --- internal/apis/kfd/v1alpha2/eks/schema_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/schema_test.go b/internal/apis/kfd/v1alpha2/eks/schema_test.go index 6b2b6ce7e..e8483e1b4 100644 --- a/internal/apis/kfd/v1alpha2/eks/schema_test.go +++ b/internal/apis/kfd/v1alpha2/eks/schema_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package eks_test import ( @@ -38,7 +40,7 @@ func Test_ExtraSchemaValidator_Validate(t *testing.T) { confPath: "test/schema/invalid.yaml", wantErr: true, wantErrMsg: "error while unmarshalling file from test/schema/invalid.yaml" + - " :yaml: line 1: did not find expected ',' or '}'", + " :yaml: line 4 : did not find expected ',' or '}'", }, } From c40d229fd5593aa1ab28f3ff8a5bec645eaaf51b Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 27 Feb 2023 17:22:55 +0100 Subject: [PATCH 139/383] fix: amend schema_test case --- internal/apis/kfd/v1alpha2/eks/schema_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/schema_test.go b/internal/apis/kfd/v1alpha2/eks/schema_test.go index e8483e1b4..9e1d4fe8e 100644 --- a/internal/apis/kfd/v1alpha2/eks/schema_test.go +++ b/internal/apis/kfd/v1alpha2/eks/schema_test.go @@ -40,7 +40,7 @@ func Test_ExtraSchemaValidator_Validate(t *testing.T) { confPath: "test/schema/invalid.yaml", wantErr: true, wantErrMsg: "error while unmarshalling file from test/schema/invalid.yaml" + - " :yaml: line 4 : did not find expected ',' or '}'", + " :yaml: line 4: did not find expected ',' or '}'", }, } From 0c48b4dadf6a25d192ced04acf6c64687c2ffa2e Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 27 Feb 2023 17:09:41 +0100 Subject: [PATCH 140/383] chore: bump go and goreleaser versions --- .drone.yml | 16 ++++++++-------- .goreleaser.yml | 12 ++++++------ .rules/.golangci.yml | 5 +++++ Makefile | 8 ++++---- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0ac2eced7..8b4ead298 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ name: main steps: - name: prepare - image: quay.io/sighup/golang:1.19.4 + image: quay.io/sighup/golang:1.19.6 depends_on: - clone pull: always @@ -17,7 +17,7 @@ steps: - go mod download - name: license - image: quay.io/sighup/golang:1.19.4 + image: quay.io/sighup/golang:1.19.6 depends_on: - clone pull: always @@ -30,7 +30,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: lint - image: quay.io/sighup/golang:1.19.4 + image: quay.io/sighup/golang:1.19.6 depends_on: - prepare pull: always @@ -43,7 +43,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-unit - image: quay.io/sighup/golang:1.19.4 + image: quay.io/sighup/golang:1.19.6 depends_on: - prepare pull: always @@ -56,7 +56,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-integration - image: quay.io/sighup/golang:1.19.4 + image: quay.io/sighup/golang:1.19.6 depends_on: - prepare commands: @@ -71,7 +71,7 @@ steps: from_secret: NETRC_FILE - name: test-e2e - image: quay.io/sighup/golang:1.19.4 + image: quay.io/sighup/golang:1.19.6 depends_on: - prepare commands: @@ -120,7 +120,7 @@ steps: from_secret: GITHUB_SSH - name: build - image: ghcr.io/goreleaser/goreleaser:v1.11.4 + image: ghcr.io/goreleaser/goreleaser:v1.15.2 depends_on: - prepare pull: always @@ -143,7 +143,7 @@ steps: from_secret: GITHUB_TOKEN - name: build-release - image: ghcr.io/goreleaser/goreleaser:v1.11.4 + image: ghcr.io/goreleaser/goreleaser:v1.15.2 pull: always depends_on: - lint diff --git a/.goreleaser.yml b/.goreleaser.yml index 4e8ff3b12..d4e40ced6 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -20,12 +20,12 @@ builds: ldflags: - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 + - name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} checksum: name_template: 'checksums.txt' snapshot: diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml index 528e5f572..7038229d7 100644 --- a/.rules/.golangci.yml +++ b/.rules/.golangci.yml @@ -55,6 +55,11 @@ linters-settings: errchkjson: check-error-free-encoding: false report-no-exported: true + errorlint: + errorf: false + errorf-multi: true + asserts: true + comparison: true exhaustive: check-generated: true default-signifies-exhaustive: true diff --git a/Makefile b/Makefile index a7c6e8a87..724283550 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ _PROJECT_DIRECTORY = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) -_GOLANG_IMAGE = golang:1.19.4 +_GOLANG_IMAGE = golang:1.19.6 _PROJECTNAME = furyctl _GOARCH = "amd64" _BIN_OPEN = "open" @@ -73,7 +73,7 @@ mod-check-upgrades: mod-upgrade: @go get -u ./... && go mod tidy -.PHONY: generate license-add license-check +.PHONY: license-add license-check license-add: @addlicense -c "SIGHUP s.r.l" -y 2017-present -v -l bsd \ @@ -167,12 +167,12 @@ clean: deps build: @export GO_VERSION=$$(go version | cut -d ' ' -f 3) && \ goreleaser check && \ - goreleaser release --debug --snapshot --rm-dist + goreleaser release --debug --snapshot --clean release: @export GO_VERSION=$$(go version | cut -d ' ' -f 3) && \ goreleaser check && \ - goreleaser --debug release --rm-dist + goreleaser release --debug --clean # Helpers From 8825c404f62ed53f016dcf590c594bc5ce14b4e8 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 24 Feb 2023 18:45:25 +0100 Subject: [PATCH 141/383] refactor: first round of refinement on dump template cmd --- cmd/dump/template.go | 160 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 29 deletions(-) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 973acc5e0..0e9a0e4e6 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -5,6 +5,7 @@ package dump import ( + "errors" "fmt" "os" "path/filepath" @@ -13,24 +14,39 @@ import ( "github.com/spf13/cobra" "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" cobrax "github.com/sighupio/furyctl/internal/x/cobra" + netx "github.com/sighupio/furyctl/internal/x/net" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -var ErrSourceDirDoesNotExist = fmt.Errorf("source directory does not exist") +const ( + source = "templates/distribution" + suffix = ".tpl" + defaultsFileName = "furyctl-defaults.yaml" +) + +var ( + ErrSourceDirDoesNotExist = errors.New("source directory does not exist") + ErrParsingFlag = errors.New("error while parsing flag") +) -type templateConfig struct { - DryRun bool - NoOverwrite bool +type TemplateCmdFlags struct { + DryRun bool + NoOverwrite bool + OutDir string + FuryctlPath string + DistroLocation string } func NewTemplateCmd(tracker *analytics.Tracker) *cobra.Command { var cmdEvent analytics.Event - cfg := templateConfig{} - templateCmd := &cobra.Command{ + cmd := &cobra.Command{ Use: "template", Short: "Renders the distribution's manifests from a template and a configuration file", Long: `Generates a folder with the Kustomization project for deploying Kubernetes Fury Distribution into a cluster. @@ -40,30 +56,57 @@ The generated folder will be created starting from a provided template and the p PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, - RunE: func(_ *cobra.Command, _ []string) error { - source := "templates/distribution" - target := "target" - suffix := ".tpl" - distributionFilePath := "furyctl-defaults.yaml" - furyctlFilePath := "furyctl.yaml" - - distributionFile, err := yamlx.FromFileV2[map[any]any](distributionFilePath) + RunE: func(cmd *cobra.Command, _ []string) error { + // Get flags. + flags, err := getDumpTemplateCmdFlags(cmd, tracker, cmdEvent) + if err != nil { + return err + } + + // Init collaborators. + client := netx.NewGoGetterClient() + distrodl := distribution.NewDownloader(client) + + // Download the distribution. + logrus.Info("Downloading distribution...") + res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error downloading distribution: %w", err) + } + + // Validate the furyctl.yaml file. + logrus.Info("Validating configuration file...") + if err := config.Validate(flags.FuryctlPath, res.RepoPath); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating configuration file: %w", err) + } + + defaultsFilePath := filepath.Join(res.RepoPath, defaultsFileName) + + distributionFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("%s - %w", distributionFilePath, err) + return fmt.Errorf("%s - %w", defaultsFilePath, err) } - furyctlFile, err := yamlx.FromFileV2[map[any]any](furyctlFilePath) + furyctlFile, err := yamlx.FromFileV2[map[any]any](flags.FuryctlPath) if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("%s - %w", furyctlFilePath, err) + return fmt.Errorf("%s - %w", flags.FuryctlPath, err) } - if _, err := os.Stat(source); os.IsNotExist(err) { + sourcePath := filepath.Join(res.RepoPath, source) + + if _, err := os.Stat(sourcePath); os.IsNotExist(err) { cmdEvent.AddErrorMessage(ErrSourceDirDoesNotExist) tracker.Track(cmdEvent) @@ -131,8 +174,8 @@ The generated folder will be created starting from a provided template and the p return fmt.Errorf("error writing config file: %w", err) } - if !cfg.NoOverwrite { - if err = os.RemoveAll(target); err != nil { + if !flags.NoOverwrite { + if err = os.RemoveAll(flags.OutDir); err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) @@ -141,13 +184,13 @@ The generated folder will be created starting from a provided template and the p } templateModel, err := template.NewTemplateModel( - source, - target, + sourcePath, + flags.OutDir, confPath, outDirPath, suffix, - cfg.NoOverwrite, - cfg.DryRun, + flags.NoOverwrite, + flags.DryRun, ) if err != nil { cmdEvent.AddErrorMessage(err) @@ -171,18 +214,77 @@ The generated folder will be created starting from a provided template and the p }, } - templateCmd.Flags().BoolVar( - &cfg.DryRun, + cmd.Flags().Bool( "dry-run", false, "Furyctl will try its best to generate the manifests despite the errors", ) - templateCmd.Flags().BoolVar( - &cfg.NoOverwrite, + + cmd.Flags().Bool( "no-overwrite", false, "Stop if target directory is not empty", ) - return templateCmd + cmd.Flags().StringP( + "out-dir", + "o", + "distribution", + "Location where to generate the distribution template. Defaults to distribution folder in the current "+ + "directory.", + ) + + cmd.Flags().StringP( + "distro-location", + "", + "", + "Location where to download schemas, defaults and the distribution manifest. "+ + "It can either be a local path(eg: /path/to/fury/distribution) or "+ + "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1). "+ + "Any format supported by hashicorp/go-getter can be used.", + ) + + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the configuration file", + ) + + return cmd +} + +func getDumpTemplateCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (TemplateCmdFlags, error) { + dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "dry-run") + } + + noOverwrite, err := cmdutil.BoolFlag(cmd, "no-overwrite", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "no-overwrite") + } + + outDir, err := cmdutil.StringFlag(cmd, "out-dir", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "out-dir") + } + + distroLocation, err := cmdutil.StringFlag(cmd, "distro-location", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "distro-location") + } + + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "config") + } + + return TemplateCmdFlags{ + DryRun: dryRun, + NoOverwrite: noOverwrite, + OutDir: outDir, + DistroLocation: distroLocation, + FuryctlPath: furyctlPath, + }, nil } From 9dfcfea958cadfb0d3030dc543af29df20c8b609 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 27 Feb 2023 12:16:36 +0100 Subject: [PATCH 142/383] feat: split dump template logic --- cmd/dump/template.go | 127 ++----------------------- internal/distribution/manifest.go | 152 ++++++++++++++++++++++++++++++ test/e2e/furyctl_test.go | 1 + 3 files changed, 163 insertions(+), 117 deletions(-) create mode 100644 internal/distribution/manifest.go diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 0e9a0e4e6..3703369af 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -7,8 +7,6 @@ package dump import ( "errors" "fmt" - "os" - "path/filepath" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -17,23 +15,12 @@ import ( "github.com/sighupio/furyctl/internal/cmd/cmdutil" "github.com/sighupio/furyctl/internal/config" "github.com/sighupio/furyctl/internal/distribution" - "github.com/sighupio/furyctl/internal/merge" - "github.com/sighupio/furyctl/internal/template" cobrax "github.com/sighupio/furyctl/internal/x/cobra" netx "github.com/sighupio/furyctl/internal/x/net" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const ( - source = "templates/distribution" - suffix = ".tpl" - defaultsFileName = "furyctl-defaults.yaml" -) - -var ( - ErrSourceDirDoesNotExist = errors.New("source directory does not exist") - ErrParsingFlag = errors.New("error while parsing flag") -) +var ErrParsingFlag = errors.New("error while parsing flag") type TemplateCmdFlags struct { DryRun bool @@ -86,16 +73,6 @@ The generated folder will be created starting from a provided template and the p return fmt.Errorf("error while validating configuration file: %w", err) } - defaultsFilePath := filepath.Join(res.RepoPath, defaultsFileName) - - distributionFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("%s - %w", defaultsFilePath, err) - } - furyctlFile, err := yamlx.FromFileV2[map[any]any](flags.FuryctlPath) if err != nil { cmdEvent.AddErrorMessage(err) @@ -104,110 +81,26 @@ The generated folder will be created starting from a provided template and the p return fmt.Errorf("%s - %w", flags.FuryctlPath, err) } - sourcePath := filepath.Join(res.RepoPath, source) - - if _, err := os.Stat(sourcePath); os.IsNotExist(err) { - cmdEvent.AddErrorMessage(ErrSourceDirDoesNotExist) - tracker.Track(cmdEvent) - - return ErrSourceDirDoesNotExist - } - - merger := merge.NewMerger( - merge.NewDefaultModel(distributionFile, ".data"), - merge.NewDefaultModel(furyctlFile, ".spec.distribution"), - ) + logrus.Info("Generating distribution manifests...") - _, err = merger.Merge() - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error merging files: %w", err) - } - - reverseMerger := merge.NewMerger( - *merger.GetCustom(), - *merger.GetBase(), - ) - - _, err = reverseMerger.Merge() - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error merging files: %w", err) - } - - tmplCfg, err := template.NewConfig(reverseMerger, reverseMerger, []string{}) - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error creating template config: %w", err) - } - - outYaml, err := yamlx.MarshalV2(tmplCfg) - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error marshaling template config: %w", err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-dist-") - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error creating temporary directory: %w", err) - } - - confPath := filepath.Join(outDirPath, "config.yaml") - - logrus.Debugf("config path = %s", confPath) - - if err = os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error writing config file: %w", err) - } - - if !flags.NoOverwrite { - if err = os.RemoveAll(flags.OutDir); err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - - return fmt.Errorf("error removing target directory: %w", err) - } - } - - templateModel, err := template.NewTemplateModel( - sourcePath, + distroManBuilder := distribution.NewManifestBuilder( + furyctlFile, + res.RepoPath, flags.OutDir, - confPath, - outDirPath, - suffix, flags.NoOverwrite, flags.DryRun, ) - if err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) - return fmt.Errorf("error creating template model: %w", err) - } - - err = templateModel.Generate() - if err != nil { + if err := distroManBuilder.Build(); err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("error generating from template: %w", err) + return fmt.Errorf("error while generating distribution manifests: %w", err) } - cmdEvent.AddSuccessMessage("Distribution template generated successfully") + logrus.Info("Distribution manifests generated successfully") + + cmdEvent.AddSuccessMessage("Distribution manifests generated successfully") tracker.Track(cmdEvent) return nil diff --git a/internal/distribution/manifest.go b/internal/distribution/manifest.go new file mode 100644 index 000000000..4da692bc9 --- /dev/null +++ b/internal/distribution/manifest.go @@ -0,0 +1,152 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/template" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +const ( + source = "templates/distribution" + defaultsFileName = "furyctl-defaults.yaml" + suffix = ".tpl" +) + +var ErrSourceDirDoesNotExist = errors.New("source directory does not exist") + +type ManifestBuilder struct { + furyctlFile map[any]any + distroPath string + outDir string + noOverwrite bool + dryRun bool +} + +func NewManifestBuilder( + furyctlFile map[any]any, + distroPath, + outDir string, + noOverwrite, + dryRun bool, +) *ManifestBuilder { + return &ManifestBuilder{ + furyctlFile: furyctlFile, + distroPath: distroPath, + outDir: outDir, + noOverwrite: noOverwrite, + dryRun: dryRun, + } +} + +func (m *ManifestBuilder) Build() error { + defaultsFile, err := m.defaultsFile() + if err != nil { + return fmt.Errorf("error getting defaults file: %w", err) + } + + sourcePath, err := m.sourcePath() + if err != nil { + return fmt.Errorf("error getting source path: %w", err) + } + + merger := merge.NewMerger( + merge.NewDefaultModel(defaultsFile, ".data"), + merge.NewDefaultModel(m.furyctlFile, ".spec.distribution"), + ) + + _, err = merger.Merge() + if err != nil { + return fmt.Errorf("error merging files: %w", err) + } + + reverseMerger := merge.NewMerger( + *merger.GetCustom(), + *merger.GetBase(), + ) + + _, err = reverseMerger.Merge() + if err != nil { + return fmt.Errorf("error merging files: %w", err) + } + + tmplCfg, err := template.NewConfig(reverseMerger, reverseMerger, []string{"source/terraform", ".gitignore"}) + if err != nil { + return fmt.Errorf("error creating template config: %w", err) + } + + outYaml, err := yamlx.MarshalV2(tmplCfg) + if err != nil { + return fmt.Errorf("error marshaling template config: %w", err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-") + if err != nil { + return fmt.Errorf("error creating temporary directory: %w", err) + } + + confPath := filepath.Join(outDirPath, "config.yaml") + + logrus.Debugf("config path = %s", confPath) + + if err = os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return fmt.Errorf("error writing config file: %w", err) + } + + if !m.noOverwrite { + if err = os.RemoveAll(m.outDir); err != nil { + return fmt.Errorf("error removing target directory: %w", err) + } + } + + templateModel, err := template.NewTemplateModel( + sourcePath, + m.outDir, + confPath, + outDirPath, + suffix, + m.noOverwrite, + m.dryRun, + ) + if err != nil { + return fmt.Errorf("error creating template model: %w", err) + } + + err = templateModel.Generate() + if err != nil { + return fmt.Errorf("error generating from template: %w", err) + } + + return nil +} + +func (m *ManifestBuilder) defaultsFile() (map[any]any, error) { + defaultsFilePath := filepath.Join(m.distroPath, defaultsFileName) + + defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) + if err != nil { + return nil, fmt.Errorf("%s - %w", defaultsFilePath, err) + } + + return defaultsFile, nil +} + +func (m *ManifestBuilder) sourcePath() (string, error) { + sourcePath := filepath.Join(m.distroPath, source) + + if _, err := os.Stat(sourcePath); os.IsNotExist(err) { + return "", ErrSourceDirDoesNotExist + } + + return sourcePath, nil +} diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 567292374..2ef3bf342 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -337,6 +337,7 @@ var ( "dump", "template", "--debug", "--workdir", workdir, + "--distro-location", workdir, "--disable-analytics", "--log", "stdout", } From ee96ac38bf8b942db367100ea9bb4c5ded28d881 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 27 Feb 2023 17:03:07 +0100 Subject: [PATCH 143/383] feat: added -skip-validation on dump template --- cmd/dump/template.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 3703369af..34a0b8d3f 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -7,13 +7,13 @@ package dump import ( "errors" "fmt" + "github.com/sighupio/furyctl/internal/config" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" - "github.com/sighupio/furyctl/internal/config" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" netx "github.com/sighupio/furyctl/internal/x/net" @@ -25,6 +25,7 @@ var ErrParsingFlag = errors.New("error while parsing flag") type TemplateCmdFlags struct { DryRun bool NoOverwrite bool + SkipValidation bool OutDir string FuryctlPath string DistroLocation string @@ -64,13 +65,15 @@ The generated folder will be created starting from a provided template and the p return fmt.Errorf("error downloading distribution: %w", err) } - // Validate the furyctl.yaml file. - logrus.Info("Validating configuration file...") - if err := config.Validate(flags.FuryctlPath, res.RepoPath); err != nil { - cmdEvent.AddErrorMessage(err) - tracker.Track(cmdEvent) + if !flags.SkipValidation { + // Validate the furyctl.yaml file. + logrus.Info("Validating configuration file...") + if err := config.Validate(flags.FuryctlPath, res.RepoPath); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) - return fmt.Errorf("error while validating configuration file: %w", err) + return fmt.Errorf("error while validating configuration file: %w", err) + } } furyctlFile, err := yamlx.FromFileV2[map[any]any](flags.FuryctlPath) @@ -144,6 +147,12 @@ The generated folder will be created starting from a provided template and the p "Path to the configuration file", ) + cmd.Flags().Bool( + "skip-validation", + false, + "Skip validation of the configuration file", + ) + return cmd } @@ -158,6 +167,11 @@ func getDumpTemplateCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmd return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "no-overwrite") } + skipValidation, err := cmdutil.BoolFlag(cmd, "skip-validation", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-validation") + } + outDir, err := cmdutil.StringFlag(cmd, "out-dir", tracker, cmdEvent) if err != nil { return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "out-dir") @@ -176,6 +190,7 @@ func getDumpTemplateCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmd return TemplateCmdFlags{ DryRun: dryRun, NoOverwrite: noOverwrite, + SkipValidation: skipValidation, OutDir: outDir, DistroLocation: distroLocation, FuryctlPath: furyctlPath, From 5b684b9ea5a935246bcfdcc9f89e3b72f0901dd5 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 27 Feb 2023 17:03:22 +0100 Subject: [PATCH 144/383] fix: e2e tests --- cmd/dump/template.go | 2 +- .../dump/template/complex-dry-run/kfd.yaml | 35 +++++++++++++++++++ test/data/e2e/dump/template/complex/kfd.yaml | 35 +++++++++++++++++++ .../kfd.yaml | 35 +++++++++++++++++++ .../no-distribution-yaml/furyctl.yaml | 8 +++++ .../dump/template/no-furyctl-yaml/kfd.yaml | 35 +++++++++++++++++++ .../e2e/dump/template/simple-dry-run/kfd.yaml | 35 +++++++++++++++++++ test/data/e2e/dump/template/simple/kfd.yaml | 35 +++++++++++++++++++ test/e2e/furyctl_test.go | 6 ++-- 9 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 test/data/e2e/dump/template/complex-dry-run/kfd.yaml create mode 100644 test/data/e2e/dump/template/complex/kfd.yaml create mode 100644 test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml create mode 100644 test/data/e2e/dump/template/no-distribution-yaml/furyctl.yaml create mode 100644 test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml create mode 100644 test/data/e2e/dump/template/simple-dry-run/kfd.yaml create mode 100644 test/data/e2e/dump/template/simple/kfd.yaml diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 34a0b8d3f..f801e1a5b 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -7,13 +7,13 @@ package dump import ( "errors" "fmt" - "github.com/sighupio/furyctl/internal/config" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/config" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" netx "github.com/sighupio/furyctl/internal/x/net" diff --git a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml new file mode 100644 index 000000000..332313e8a --- /dev/null +++ b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: "*" diff --git a/test/data/e2e/dump/template/complex/kfd.yaml b/test/data/e2e/dump/template/complex/kfd.yaml new file mode 100644 index 000000000..332313e8a --- /dev/null +++ b/test/data/e2e/dump/template/complex/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: "*" diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml new file mode 100644 index 000000000..332313e8a --- /dev/null +++ b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: "*" diff --git a/test/data/e2e/dump/template/no-distribution-yaml/furyctl.yaml b/test/data/e2e/dump/template/no-distribution-yaml/furyctl.yaml new file mode 100644 index 000000000..246e7604f --- /dev/null +++ b/test/data/e2e/dump/template/no-distribution-yaml/furyctl.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +spec: + distribution: + test: + hello: testValue diff --git a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml new file mode 100644 index 000000000..332313e8a --- /dev/null +++ b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: "*" diff --git a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml new file mode 100644 index 000000000..332313e8a --- /dev/null +++ b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: "*" diff --git a/test/data/e2e/dump/template/simple/kfd.yaml b/test/data/e2e/dump/template/simple/kfd.yaml new file mode 100644 index 000000000..332313e8a --- /dev/null +++ b/test/data/e2e/dump/template/simple/kfd.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: v1.24.1 +modules: + auth: v0.0.1 + aws: v2.0.0 + dr: v1.9.3 + ingress: v1.12.2 + logging: v2.0.3 + monitoring: v1.14.2 + opa: v1.7.0 + networking: v1.10.0 +kubernetes: + eks: + version: 1.24 + installer: v1.10.0 +furyctlSchemas: + eks: + - apiVersion: kfd.sighup.io/v1alpha2 + kind: EKSCluster +tools: + common: + furyagent: + version: 0.3.0 + kubectl: + version: 1.24.9 + kustomize: + version: 3.5.3 + terraform: + version: 0.15.4 + eks: + awscli: + version: "*" diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 2ef3bf342..18820f5ec 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -337,9 +337,11 @@ var ( "dump", "template", "--debug", "--workdir", workdir, - "--distro-location", workdir, + "--distro-location", ".", + "--out-dir", "target", "--disable-analytics", "--log", "stdout", + "--skip-validation", } if dryRun { args = append(args, "--dry-run") @@ -362,7 +364,7 @@ var ( out, err := FuryctlDumpTemplate(bp, false) Expect(err).To(HaveOccurred()) - Expect(out).To(ContainSubstring("furyctl-defaults.yaml: no such file or directory")) + Expect(out).To(ContainSubstring("unsupported KFD version")) }) It("fails if no furyctl.yaml file is found", func() { From 5f8d7cdcf09be4540ef784e047736f61f5bba75c Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 27 Feb 2023 17:39:51 +0100 Subject: [PATCH 145/383] chore: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1d4fc025..12bb41329 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ needed to create a Kubernetes Fury cluster. > 🔥 **Advanced Tip** > -> Using the command `furyctl dump template` with the flag `-w` pointing to the local location of the repository `fury-distribution`, +> Using the command `furyctl dump template` with the flag `--distro-location` pointing to the local location of the repository `fury-distribution`, > will run the template engine on the modules and generate the final manifests that will be applied to the cluster. #### Cluster creation From 97f3603f63560bde19dd2379535eed2afea95903 Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 27 Feb 2023 18:56:31 +0100 Subject: [PATCH 146/383] chore: rename ManifestBuilder to IACBuilder, update dump template command description --- cmd/dump/template.go | 8 ++++---- internal/distribution/{manifest.go => iac.go} | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) rename internal/distribution/{manifest.go => iac.go} (92%) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index f801e1a5b..b97899b78 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -36,9 +36,9 @@ func NewTemplateCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "template", - Short: "Renders the distribution's manifests from a template and a configuration file", - Long: `Generates a folder with the Kustomization project for deploying Kubernetes Fury Distribution into a cluster. -The generated folder will be created starting from a provided template and the parameters set in a configuration file that is merged with default values.`, + Short: "Renders the distribution's infrastructure code from template files and a configuration file", + Long: `Generates a folder with the Terraform and Kustomization code for deploying the Kubernetes Fury Distribution into a cluster. +The generated folder will be created starting from a provided templates folder and the parameters set in a configuration file that is merged with default values.`, SilenceUsage: true, SilenceErrors: true, PreRun: func(cmd *cobra.Command, _ []string) { @@ -86,7 +86,7 @@ The generated folder will be created starting from a provided template and the p logrus.Info("Generating distribution manifests...") - distroManBuilder := distribution.NewManifestBuilder( + distroManBuilder := distribution.NewIACBuilder( furyctlFile, res.RepoPath, flags.OutDir, diff --git a/internal/distribution/manifest.go b/internal/distribution/iac.go similarity index 92% rename from internal/distribution/manifest.go rename to internal/distribution/iac.go index 4da692bc9..6ce7b0641 100644 --- a/internal/distribution/manifest.go +++ b/internal/distribution/iac.go @@ -25,7 +25,7 @@ const ( var ErrSourceDirDoesNotExist = errors.New("source directory does not exist") -type ManifestBuilder struct { +type IACBuilder struct { furyctlFile map[any]any distroPath string outDir string @@ -33,14 +33,14 @@ type ManifestBuilder struct { dryRun bool } -func NewManifestBuilder( +func NewIACBuilder( furyctlFile map[any]any, distroPath, outDir string, noOverwrite, dryRun bool, -) *ManifestBuilder { - return &ManifestBuilder{ +) *IACBuilder { + return &IACBuilder{ furyctlFile: furyctlFile, distroPath: distroPath, outDir: outDir, @@ -49,7 +49,7 @@ func NewManifestBuilder( } } -func (m *ManifestBuilder) Build() error { +func (m *IACBuilder) Build() error { defaultsFile, err := m.defaultsFile() if err != nil { return fmt.Errorf("error getting defaults file: %w", err) @@ -130,7 +130,7 @@ func (m *ManifestBuilder) Build() error { return nil } -func (m *ManifestBuilder) defaultsFile() (map[any]any, error) { +func (m *IACBuilder) defaultsFile() (map[any]any, error) { defaultsFilePath := filepath.Join(m.distroPath, defaultsFileName) defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) @@ -141,7 +141,7 @@ func (m *ManifestBuilder) defaultsFile() (map[any]any, error) { return defaultsFile, nil } -func (m *ManifestBuilder) sourcePath() (string, error) { +func (m *IACBuilder) sourcePath() (string, error) { sourcePath := filepath.Join(m.distroPath, source) if _, err := os.Stat(sourcePath); os.IsNotExist(err) { From 2d370650d8fe75de68991cbc4f48267dd02d3648 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 28 Feb 2023 14:31:21 +0100 Subject: [PATCH 147/383] chore: draft region and tags passing in bootstrap template --- configs/provisioners/bootstrap/aws/main.tf.tpl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index dd1da4c61..09ca9ba7c 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -10,6 +10,21 @@ terraform { key = "{{ .terraform.backend.s3.keyPrefix }}/infrastructure.json" region = "{{ .terraform.backend.s3.region }}" } + + required_providers { + aws = { + source = "hashicorp/aws" + } + } +} + +provider "aws" { + region = "{{ .global.region }}" + default_tags = { + tags = { + {{range $k, $v := .global.tags}}$k = $v{{end}} + } + } } module "vpc-and-vpn" { From c589e7d221c10e758dd4f2f7ec914c8fbe971c05 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 28 Feb 2023 14:33:36 +0100 Subject: [PATCH 148/383] chore: draft global var passing in templating function of kube and infra phases --- .../provisioners/bootstrap/aws/main.tf.tpl | 8 +++--- configs/provisioners/cluster/eks/data.tf | 19 +++++++++++++ configs/provisioners/cluster/eks/main.tf.tpl | 27 +++++++++++++++++++ .../kfd/v1alpha2/eks/create/infrastructure.go | 4 +++ .../kfd/v1alpha2/eks/create/kubernetes.go | 4 +++ 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 configs/provisioners/cluster/eks/data.tf diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index 09ca9ba7c..528c15cdb 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -19,10 +19,12 @@ terraform { } provider "aws" { - region = "{{ .global.region }}" - default_tags = { + region = "{{ .spec.region }}" + default_tags { tags = { - {{range $k, $v := .global.tags}}$k = $v{{end}} + {{- range $k, $v := .spec.tags }} + {{ $k }} = "{{ $v }}" + {{- end}} } } } diff --git a/configs/provisioners/cluster/eks/data.tf b/configs/provisioners/cluster/eks/data.tf new file mode 100644 index 000000000..58559e9e9 --- /dev/null +++ b/configs/provisioners/cluster/eks/data.tf @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +data "aws_eks_cluster" "fury" { + name = var.cluster_name + depends_on = [ + module.fury + ] +} + +data "aws_eks_cluster_auth" "fury" { + name = var.cluster_name + depends_on = [ + module.fury + ] +} diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index 9ba8e42d4..5dfa5b7cb 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -16,6 +16,33 @@ terraform { key = "{{ .terraform.backend.s3.keyPrefix }}/cluster.json" region = "{{ .terraform.backend.s3.region }}" } + + required_providers { + aws = { + source = "hashicorp/aws" + } + kubernetes = { + source = "hashicorp/kubernetes" + } + } +} + +provider "aws" { + region = "{{ .spec.region }}" + default_tags { + tags = { + {{- range $k, $v := .spec.tags }} + {{ $k }} = "{{ $v }}" + {{- end}} + } + } +} + +provider "kubernetes" { + host = data.aws_eks_cluster.fury.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.fury.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.fury.token + load_config_file = false } module "fury" { diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 3594ad068..550d5acbb 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -228,6 +228,10 @@ func (i *Infrastructure) copyFromTemplate() error { eksInstallerPath := path.Join(i.Path, "..", "vendor", "installers", "eks", "modules", "vpc-and-vpn") cfg.Data = map[string]map[any]any{ + "spec": { + "region": i.furyctlConf.Spec.Region, + "tags": i.furyctlConf.Spec.Tags, + }, "kubernetes": { "installerPath": eksInstallerPath, }, diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index a12db9112..1c1d78742 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -212,6 +212,10 @@ func (k *Kubernetes) copyFromTemplate() error { eksInstallerPath := path.Join(k.Path, "..", "vendor", "installers", "eks", "modules", "eks") tfConfVars := map[string]map[any]any{ + "spec": { + "region": k.furyctlConf.Spec.Region, + "tags": k.furyctlConf.Spec.Tags, + }, "kubernetes": { "installerPath": eksInstallerPath, "tfVersion": k.kfdManifest.Tools.Common.Terraform.Version, From 97dab0fcf73b1c4907c09fd4ac7da4e833f68102 Mon Sep 17 00:00:00 2001 From: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:55:33 +0100 Subject: [PATCH 149/383] chore: remove validation for AWS_DEFAULT_REGION when use resource of kind EKSCluster --- internal/dependencies/envvars/validator.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/internal/dependencies/envvars/validator.go b/internal/dependencies/envvars/validator.go index 001c3d40f..81fb0c2f9 100644 --- a/internal/dependencies/envvars/validator.go +++ b/internal/dependencies/envvars/validator.go @@ -5,10 +5,10 @@ package envvars import ( - "errors" - "fmt" - "os" - "strings" + "errors" + "fmt" + "os" + "strings" ) var ( @@ -36,12 +36,6 @@ func (*Validator) checkEKSCluster() ([]string, []error) { var missingAwsVars []string - if os.Getenv("AWS_DEFAULT_REGION") == "" { - errs = append(errs, fmt.Errorf("%w: AWS_DEFAULT_REGION", ErrMissingRequiredEnvVar)) - } else { - oks = append(oks, "AWS_DEFAULT_REGION") - } - if os.Getenv("AWS_PROFILE") != "" { oks = append(oks, "AWS_PROFILE") From b21b0168034cc8fb9f3d122ae9056ba99ab4c1dd Mon Sep 17 00:00:00 2001 From: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> Date: Tue, 7 Mar 2023 11:46:50 +0100 Subject: [PATCH 150/383] fix: remove AWS_DEFAULT_REGION from e2e test --- test/e2e/furyctl_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 18820f5ec..4740bbc24 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -197,12 +197,13 @@ var ( } It("should report an error when dependencies are missing", func() { - RestoreEnvVars := BackupEnvVars("PATH", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_DEFAULT_REGION") + RestoreEnvVars := BackupEnvVars("PATH", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY") defer RestoreEnvVars() os.Unsetenv("AWS_ACCESS_KEY_ID") os.Unsetenv("AWS_SECRET_ACCESS_KEY") os.Unsetenv("AWS_DEFAULT_REGION") + os.Unsetenv("AWS_REGION") out, err := FuryctlValidateDependencies("../data/e2e/validate/dependencies/missing", "/tmp") @@ -211,7 +212,6 @@ var ( Expect(out).To(ContainSubstring("kubectl:")) Expect(out).To(ContainSubstring("kustomize:")) Expect(out).To(ContainSubstring("furyagent:")) - Expect(out).To(ContainSubstring("missing required environment variable: AWS_DEFAULT_REGION")) Expect(out).To(ContainSubstring("missing environment variables, either AWS_PROFILE or the " + "following environment variables must be set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")) }) @@ -221,7 +221,6 @@ var ( "PATH", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", - "AWS_DEFAULT_REGION", "FURYCTL_MIXPANEL_TOKEN", ) defer RestoreEnvVars() @@ -232,6 +231,7 @@ var ( os.Unsetenv("AWS_ACCESS_KEY_ID") os.Unsetenv("AWS_SECRET_ACCESS_KEY") os.Unsetenv("AWS_DEFAULT_REGION") + os.Unsetenv("AWS_REGION") os.Unsetenv("FURYCTL_MIXPANEL_TOKEN") out, err := FuryctlValidateDependencies(bp, bp) @@ -249,7 +249,6 @@ var ( Expect(out).To( ContainSubstring("terraform: wrong tool version - installed = 0.15.3, expected = 0.15.4"), ) - Expect(out).To(ContainSubstring("missing required environment variable: AWS_DEFAULT_REGION")) Expect(out).To(ContainSubstring("missing environment variables, either AWS_PROFILE or the " + "following environment variables must be set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")) }) @@ -259,7 +258,6 @@ var ( "PATH", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", - "AWS_DEFAULT_REGION", "FURYCTL_MIXPANEL_TOKEN", ) defer RestoreEnvVars() @@ -267,7 +265,6 @@ var ( bp := Abs("../data/e2e/validate/dependencies/correct") os.Setenv("PATH", bp+":"+os.Getenv("PATH")) - os.Setenv("AWS_DEFAULT_REGION", "eu-west-1") os.Setenv("FURYCTL_MIXPANEL_TOKEN", "test") out, err := FuryctlValidateDependencies(bp, bp) From e908492fd24e541c7be3496c9f9d3b9f469f1a59 Mon Sep 17 00:00:00 2001 From: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> Date: Tue, 7 Mar 2023 12:21:06 +0100 Subject: [PATCH 151/383] lint: fix internal/dependencies/envvars/validator.go --- internal/dependencies/envvars/validator.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/dependencies/envvars/validator.go b/internal/dependencies/envvars/validator.go index 81fb0c2f9..3c2319c05 100644 --- a/internal/dependencies/envvars/validator.go +++ b/internal/dependencies/envvars/validator.go @@ -5,10 +5,10 @@ package envvars import ( - "errors" - "fmt" - "os" - "strings" + "errors" + "fmt" + "os" + "strings" ) var ( From c262f4cba61ec6eae5394827d755885d23dddd12 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 6 Mar 2023 19:52:39 +0100 Subject: [PATCH 152/383] feat: added extra tool validation --- internal/apis/kfd/v1alpha2/eks/tool.go | 44 ++++++++++++++++++++++++++ internal/apis/tool.go | 27 ++++++++++++++++ internal/config/validate.go | 5 +++ 3 files changed, 76 insertions(+) create mode 100644 internal/apis/kfd/v1alpha2/eks/tool.go create mode 100644 internal/apis/tool.go diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go new file mode 100644 index 000000000..495616d90 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -0,0 +1,44 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +import ( + "fmt" + + "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/internal/tool/openvpn" + execx "github.com/sighupio/furyctl/internal/x/exec" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +var ErrOpenVPNNotInstalled = fmt.Errorf("openvpn is not installed") + +type ExtraToolsValidator struct{} + +func (x *ExtraToolsValidator) Validate(confPath string) error { + furyctlConf, err := yamlx.FromFileV3[private.EksclusterKfdV1Alpha2](confPath) + if err != nil { + return err + } + + return x.openVPN(furyctlConf) +} + +func (*ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { + executor := execx.NewStdExecutor() + + if conf.Spec.Infrastructure.Vpc != nil && + conf.Spec.Infrastructure.Vpc.Vpn != nil { + oRunner := openvpn.NewRunner(executor, openvpn.Paths{ + Openvpn: "openvpn", + }) + + if _, err := oRunner.Version(); err != nil { + return ErrOpenVPNNotInstalled + } + } + + return nil +} diff --git a/internal/apis/tool.go b/internal/apis/tool.go new file mode 100644 index 000000000..f9bcdabed --- /dev/null +++ b/internal/apis/tool.go @@ -0,0 +1,27 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package apis + +import "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" + +type ExtraToolsValidator interface { + Validate(confPath string) error +} + +func NewExtraToolsValidatorFactory(apiVersion, kind string) ExtraToolsValidator { + switch apiVersion { + case "kfd.sighup.io/v1alpha2": + switch kind { + case "EKSCluster": + return &eks.ExtraToolsValidator{} + + default: + return nil + } + + default: + return nil + } +} diff --git a/internal/config/validate.go b/internal/config/validate.go index fc1238683..8c97c21f2 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -114,6 +114,11 @@ func Validate(path, repoPath string) error { return fmt.Errorf("error while validating against extra schema rules: %w", err) } + etv := apis.NewExtraToolsValidatorFactory(miniConf.APIVersion, miniConf.Kind) + if err = etv.Validate(path); err != nil { + return fmt.Errorf("error while validating against extra tools rules: %w", err) + } + return nil } From 60bbc475918c86b5df42b7e53116860deb3e9ad6 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 7 Mar 2023 16:11:01 +0100 Subject: [PATCH 153/383] fix: e2e testing --- .../data/e2e/validate/config/correct/furyctl.yaml | 15 --------------- .../schemas/public/ekscluster-kfd-v1alpha2.json | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index 8981a8413..c4e67b115 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -32,21 +32,6 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - instances: 1 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" diff --git a/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json index 756a63d49..3637ffec6 100644 --- a/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json @@ -93,21 +93,6 @@ } } } - }, - { - "properties": { - "infrastructure": { - "properties": { - "vpc": { - "properties": { - "vpn": { - "type": "null" - } - } - } - } - } - } } ] }, From 07d12fcdd0f9a9f79fb99d8faa9f776a4c1019eb Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 7 Mar 2023 17:36:53 +0100 Subject: [PATCH 154/383] refactor: move extra tools validation to deps validation --- cmd/create/cluster.go | 2 +- cmd/delete/cluster.go | 2 +- cmd/validate/dependencies.go | 4 +- internal/apis/kfd/v1alpha2/eks/tool.go | 33 ++- internal/apis/tool.go | 11 +- internal/config/validate.go | 5 - .../dependencies/tools/test_data/furyctl.yaml | 201 ++++++++++++++++++ internal/dependencies/tools/validator.go | 21 +- internal/dependencies/tools/validator_test.go | 71 ++++++- internal/dependencies/validate.go | 6 +- .../e2e/validate/config/correct/furyctl.yaml | 15 ++ 11 files changed, 336 insertions(+), 35 deletions(-) create mode 100644 internal/dependencies/tools/test_data/furyctl.yaml diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 072ab33e0..b880f98ab 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -138,7 +138,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Init second half of collaborators. depsdl := dependencies.NewDownloader(client, basePath, flags.BinPath) - depsvl := dependencies.NewValidator(executor, flags.BinPath) + depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath) // Validate the furyctl.yaml file. logrus.Info("Validating configuration file...") diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index cf97c89b9..3bd20d2b6 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -121,7 +121,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) // Init second half of collaborators. - depsvl := dependencies.NewValidator(executor, flags.BinPath) + depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath) // Validate the dependencies. logrus.Info("Validating dependencies...") diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index b01083323..a017de842 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -71,7 +71,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { binPath = filepath.Join(homeDir, ".furyctl", "bin") } - toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath) + toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath, furyctlPath) envVarsValidator := envvars.NewValidator() @@ -83,7 +83,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { toks, terrs := toolsValidator.Validate( dres.DistroManifest, - dres.MinimalConf.Spec.ToolsConfiguration.Terraform.State, + dres.MinimalConf, ) logrus.Info("Validating environment variables...") diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index 495616d90..3f29945c6 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -15,23 +15,40 @@ import ( var ErrOpenVPNNotInstalled = fmt.Errorf("openvpn is not installed") -type ExtraToolsValidator struct{} +type ExtraToolsValidator struct { + executor execx.Executor +} + +func NewExtraToolsValidator(executor execx.Executor) *ExtraToolsValidator { + return &ExtraToolsValidator{ + executor: executor, + } +} + +func (x *ExtraToolsValidator) Validate(confPath string) ([]string, []error) { + var ( + oks []string + errs []error + ) -func (x *ExtraToolsValidator) Validate(confPath string) error { furyctlConf, err := yamlx.FromFileV3[private.EksclusterKfdV1Alpha2](confPath) if err != nil { - return err + return oks, append(errs, err) } - return x.openVPN(furyctlConf) -} + if err := x.openVPN(furyctlConf); err != nil { + errs = append(errs, err) + } else { + oks = append(oks, "openvpn") + } -func (*ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { - executor := execx.NewStdExecutor() + return oks, errs +} +func (x *ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { if conf.Spec.Infrastructure.Vpc != nil && conf.Spec.Infrastructure.Vpc.Vpn != nil { - oRunner := openvpn.NewRunner(executor, openvpn.Paths{ + oRunner := openvpn.NewRunner(x.executor, openvpn.Paths{ Openvpn: "openvpn", }) diff --git a/internal/apis/tool.go b/internal/apis/tool.go index f9bcdabed..d7f380ea7 100644 --- a/internal/apis/tool.go +++ b/internal/apis/tool.go @@ -4,18 +4,21 @@ package apis -import "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" +import ( + "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" + execx "github.com/sighupio/furyctl/internal/x/exec" +) type ExtraToolsValidator interface { - Validate(confPath string) error + Validate(confPath string) ([]string, []error) } -func NewExtraToolsValidatorFactory(apiVersion, kind string) ExtraToolsValidator { +func NewExtraToolsValidatorFactory(executor execx.Executor, apiVersion, kind string) ExtraToolsValidator { switch apiVersion { case "kfd.sighup.io/v1alpha2": switch kind { case "EKSCluster": - return &eks.ExtraToolsValidator{} + return eks.NewExtraToolsValidator(executor) default: return nil diff --git a/internal/config/validate.go b/internal/config/validate.go index 8c97c21f2..fc1238683 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -114,11 +114,6 @@ func Validate(path, repoPath string) error { return fmt.Errorf("error while validating against extra schema rules: %w", err) } - etv := apis.NewExtraToolsValidatorFactory(miniConf.APIVersion, miniConf.Kind) - if err = etv.Validate(path); err != nil { - return fmt.Errorf("error while validating against extra tools rules: %w", err) - } - return nil } diff --git a/internal/dependencies/tools/test_data/furyctl.yaml b/internal/dependencies/tools/test_data/furyctl.yaml new file mode 100644 index 000000000..e7c6edd94 --- /dev/null +++ b/internal/dependencies/tools/test_data/furyctl.yaml @@ -0,0 +1,201 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: furyctl-dev-aws +spec: + distributionVersion: "v1.24.1" + toolsConfiguration: + terraform: + state: + s3: + bucketName: furyctl-test + keyPrefix: furyctl/ + region: eu-west-1 + region: eu-west-1 + tags: + env: "test" + k8s: "awesome" + infrastructure: + vpc: + network: + cidr: 10.0.0.0/16 + subnetsCidrs: + private: + - 10.0.182.0/24 + - 10.0.172.0/24 + - 10.0.162.0/24 + public: + - 10.0.20.0/24 + - 10.0.30.0/24 + - 10.0.40.0/24 + vpn: + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + nodePoolsLaunchKind: "launch_templates" + nodePools: + - name: worker + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 1 + max: 3 + subnetIds: [] + instance: + type: t3.micro + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: + - node.kubernetes.io/role=worker:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + - name: worker-eks + ami: + id: ami-0ab303329574a0338 + owner: "363601582189" + size: + min: 1 + max: 3 + subnetIds: [] + instance: + type: t3.micro + spot: false + volumeSize: 50 + attachedTargetGroups: [] + labels: + nodepool: worker + node.kubernetes.io/role: worker + taints: [] + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" + additionalFirewallRules: [] + awsAuth: + additionalAccounts: [] + users: [] + roles: [] + distribution: + common: + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + modules: + ingress: + baseDomain: internal.fury-demo.sighup.io + nginx: + type: single + tls: + provider: certManager + secret: + cert: "{file://relative/path/to/ssl.crt}" + key: "{file://relative/path/to/ssl.key}" + ca: "{file://relative/path/to/ssl.ca}" + certManager: + clusterIssuer: + name: letsencrypt-fury + email: engineering+fury-distribution@sighup.io + type: http01 + dns: + public: + name: "fury-demo.sighup.io" + create: false + private: + create: true + name: "internal.fury-demo.sighup.io" + vpcId: "vpc123123123123" + logging: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + type: single + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + storageSize: "150Gi" + monitoring: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + policy: + overrides: + nodeSelector: {} + tolerations: [] + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + gatekeeper: + additionalExcludedNamespaces: [] + dr: + velero: + eks: + bucketName: example-velero + iamRoleArn: arn:aws:iam::123456789012:role/example-velero # private. + region: eu-west-1 + auth: + provider: + type: none + basicAuth: + username: admin + password: "{env://KFD_BASIC_AUTH_PASSWORD}" diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index 770e93d83..2433fe8ba 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/apis" itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -19,21 +20,23 @@ var ( ErrWrongToolVersion = errors.New("wrong tool version") ) -func NewValidator(executor execx.Executor, binPath string) *Validator { +func NewValidator(executor execx.Executor, binPath, furyctlPath string) *Validator { return &Validator{ executor: executor, toolFactory: NewFactory(executor, FactoryPaths{ Bin: binPath, }), + furyctlPath: furyctlPath, } } type Validator struct { executor execx.Executor toolFactory *Factory + furyctlPath string } -func (tv *Validator) Validate(kfdManifest config.KFD, tfState config.State) ([]string, []error) { +func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ([]string, []error) { var ( oks []string errs []error @@ -59,7 +62,7 @@ func (tv *Validator) Validate(kfdManifest config.KFD, tfState config.State) ([]s } } - if tfState.S3.BucketName != "" { + if miniConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName != "" { tool := tv.toolFactory.Create(itool.Awscli, "*") if err := tool.CheckBinVersion(); err != nil { errs = append(errs, err) @@ -68,5 +71,17 @@ func (tv *Validator) Validate(kfdManifest config.KFD, tfState config.State) ([]s } } + etv := apis.NewExtraToolsValidatorFactory(tv.executor, miniConf.APIVersion, miniConf.Kind) + + if etv == nil { + return oks, errs + } + + if xoks, xerrs := etv.Validate(tv.furyctlPath); len(xerrs) > 0 { + errs = append(errs, xerrs...) + } else { + oks = append(oks, xoks...) + } + return oks, errs } diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 3b4e7ee93..796f8deaa 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -8,6 +8,7 @@ package tools_test import ( "errors" + "path" "strings" "testing" @@ -20,7 +21,7 @@ func Test_Validator_Validate(t *testing.T) { testCases := []struct { desc string manifest config.KFD - state config.State + state config.Furyctl wantOks []string wantErrs []error }{ @@ -36,9 +37,17 @@ func Test_Validator_Validate(t *testing.T) { }, }, }, - state: config.State{ - S3: config.S3{ - BucketName: "test", + state: config.Furyctl{ + Spec: config.FuryctlSpec{ + ToolsConfiguration: config.ToolsConfiguration{ + Terraform: config.Terraform{ + State: config.State{ + S3: config.S3{ + BucketName: "test", + }, + }, + }, + }, }, }, wantOks: []string{ @@ -61,9 +70,17 @@ func Test_Validator_Validate(t *testing.T) { }, }, }, - state: config.State{ - S3: config.S3{ - BucketName: "test", + state: config.Furyctl{ + Spec: config.FuryctlSpec{ + ToolsConfiguration: config.ToolsConfiguration{ + Terraform: config.Terraform{ + State: config.State{ + S3: config.S3{ + BucketName: "test", + }, + }, + }, + }, }, }, wantErrs: []error{ @@ -74,12 +91,50 @@ func Test_Validator_Validate(t *testing.T) { }, wantOks: []string{"aws"}, }, + { + desc: "openvpn is installed", + manifest: config.KFD{ + Tools: config.KFDTools{ + Common: config.Common{ + Kubectl: config.Tool{Version: "1.21.1"}, + Kustomize: config.Tool{Version: "3.9.4"}, + Terraform: config.Tool{Version: "0.15.4"}, + Furyagent: config.Tool{Version: "0.3.0"}, + }, + }, + }, + state: config.Furyctl{ + APIVersion: "kfd.sighup.io/v1alpha2", + Kind: "EKSCluster", + Spec: config.FuryctlSpec{ + ToolsConfiguration: config.ToolsConfiguration{ + Terraform: config.Terraform{ + State: config.State{ + S3: config.S3{ + BucketName: "test", + }, + }, + }, + }, + }, + }, + wantOks: []string{ + "kubectl", + "kustomize", + "terraform", + "furyagent", + "aws", + "openvpn", + }, + }, } for _, tC := range testCases { tC := tC t.Run(tC.desc, func(t *testing.T) { - v := tools.NewValidator(execx.NewFakeExecutor(), "test_data") + furyctlPath := path.Join("test_data", "furyctl.yaml") + + v := tools.NewValidator(execx.NewFakeExecutor(), "test_data", furyctlPath) oks, errs := v.Validate(tC.manifest, tC.state) diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index af0958c45..7a309c08b 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -21,9 +21,9 @@ var ( errValidatingToolsConf = errors.New("errors validating tools configuration") ) -func NewValidator(executor execx.Executor, binPath string) *Validator { +func NewValidator(executor execx.Executor, binPath, furyctlPath string) *Validator { return &Validator{ - toolsValidator: tools.NewValidator(executor, binPath), + toolsValidator: tools.NewValidator(executor, binPath, furyctlPath), envVarsValidator: envvars.NewValidator(), infraValidator: toolsconf.NewValidator(executor), } @@ -38,7 +38,7 @@ type Validator struct { func (v *Validator) Validate(res distribution.DownloadResult) error { if _, errs := v.toolsValidator.Validate( res.DistroManifest, - res.MinimalConf.Spec.ToolsConfiguration.Terraform.State, + res.MinimalConf, ); len(errs) > 0 { return fmt.Errorf("%w: %v", errValidatingTools, errs) } diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index c4e67b115..8981a8413 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -32,6 +32,21 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" From 5248b9bd3318178241e6a7a2379a44ee84894ade Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 7 Mar 2023 17:50:48 +0100 Subject: [PATCH 155/383] fix: e2e tests --- .drone.yml | 2 +- test/e2e/furyctl_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 8b4ead298..f01b3c049 100644 --- a/.drone.yml +++ b/.drone.yml @@ -75,7 +75,7 @@ steps: depends_on: - prepare commands: - - apk add --update binutils curl groff openssh-client + - apk add --update binutils curl groff openssh-client openvpn # Install aws-cli - curl -sL https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub - curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/$${GLIBC_VER}/glibc-$${GLIBC_VER}.apk diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 4740bbc24..7258fffbf 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -110,6 +110,8 @@ var ( RunCmd = func(cmd string, args ...string) (string, error) { out, err := exec.Command(cmd, args...).CombinedOutput() + GinkgoWriter.Println(string(out)) + return string(out), err } From 79e9c716aec7760fee5c20fe2b5ce5593fcadf5b Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 8 Mar 2023 11:52:07 +0100 Subject: [PATCH 156/383] chore: added tools requirements in the README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 12bb41329..671bcff46 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,11 @@ furyctl validate config --config --distro-location 'g #### 2. Create a cluster +Requirements (EKSCluster): + +- AWS CLI +- OpenVPN (when filling the `vpn` field in the configuration file) + In the previous step, you have created and validated a configuration file that defines the Kubernetes cluster and its sorroundings, you can now proceed to actually creating the resources. furyctl has divided the cluster creation in three phases: `infrastructure`, `kubernetes` and `distribution`. From 9a7cba98c16fdf68272c9dbc7e87cbaa9397b3a6 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 1 Mar 2023 11:58:28 +0100 Subject: [PATCH 157/383] feat: first stub of new vpn handling --- cmd/create/cluster.go | 27 ++- .../kfd/v1alpha2/eks/create/infrastructure.go | 106 +-------- internal/apis/kfd/v1alpha2/eks/creator.go | 66 ++++-- internal/apis/kfd/v1alpha2/eks/vpn.go | 207 ++++++++++++++++++ internal/cluster/creator.go | 8 +- 5 files changed, 289 insertions(+), 125 deletions(-) create mode 100644 internal/apis/kfd/v1alpha2/eks/vpn.go diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index b880f98ab..2ef86976b 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -39,6 +39,7 @@ type ClusterCmdFlags struct { Phase string SkipPhase string BinPath string + SkipVpn bool VpnAutoConnect bool DryRun bool SkipDepsDownload bool @@ -171,11 +172,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { } } - // Auto connect to the VPN if doing complete cluster creation or skipping distribution phase. - if flags.Phase == cluster.OperationPhaseAll || flags.SkipPhase == cluster.OperationPhaseDistribution { - flags.VpnAutoConnect = true - } - // Define cluster creation paths. paths := cluster.CreatorPaths{ ConfigPath: flags.FuryctlPath, @@ -194,6 +190,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { res.DistroManifest, paths, flags.Phase, + flags.SkipVpn, flags.VpnAutoConnect, flags.DryRun, ) @@ -269,11 +266,24 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm binPath := cmdutil.StringFlagOptional(cmd, "bin-path") + skipVpn, err := cmdutil.BoolFlag(cmd, "skip-vpn", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-vpn") + } + vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) if err != nil { return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-auto-connect") } + if skipVpn && vpnAutoConnect { + return ClusterCmdFlags{}, fmt.Errorf( + "%w: %s: cannot use together with skip-vpn flag", + ErrParsingFlag, + "vpn-auto-connect", + ) + } + dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) if err != nil { return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "dry-run") @@ -306,6 +316,7 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm Phase: phase, SkipPhase: skipPhase, BinPath: binPath, + SkipVpn: skipVpn, VpnAutoConnect: vpnAutoConnect, DryRun: dryRun, SkipDepsDownload: skipDepsDownload, @@ -380,6 +391,12 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "When set will automatically connect to the created VPN in the infrastructure phase", ) + cmd.Flags().Bool( + "skip-vpn", + false, + "When set will not wait for user confirmation to connect to the VPN", + ) + cmd.Flags().String( "kubeconfig", "", diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 550d5acbb..761fb9f43 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -10,9 +10,7 @@ import ( "fmt" "io/fs" "os" - "os/exec" "path" - "path/filepath" "strings" "time" @@ -23,13 +21,11 @@ import ( "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" - "github.com/sighupio/furyctl/internal/tool/furyagent" "github.com/sighupio/furyctl/internal/tool/openvpn" "github.com/sighupio/furyctl/internal/tool/terraform" bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" - osx "github.com/sighupio/furyctl/internal/x/os" ) const SErrWrapWithStr = "%w: %s" @@ -45,7 +41,6 @@ type Infrastructure struct { furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD tfRunner *terraform.Runner - faRunner *furyagent.Runner ovRunner *openvpn.Runner dryRun bool } @@ -79,10 +74,6 @@ func NewInfrastructure( Terraform: phase.TerraformPath, }, ), - faRunner: furyagent.NewRunner(executor, furyagent.Paths{ - Furyagent: path.Join(paths.BinPath, "furyagent", kfdManifest.Tools.Common.Furyagent.Version, "furyagent"), - WorkDir: phase.SecretsPath, - }), ovRunner: openvpn.NewRunner(executor, openvpn.Paths{ WorkDir: phase.SecretsPath, Openvpn: "openvpn", @@ -91,7 +82,7 @@ func NewInfrastructure( }, nil } -func (i *Infrastructure) Exec(opts []cluster.OperationPhaseOption) error { +func (i *Infrastructure) Exec() error { logrus.Info("Creating infrastructure...") logrus.Debug("Create: running infrastructure phase...") @@ -136,73 +127,9 @@ func (i *Infrastructure) Exec(opts []cluster.OperationPhaseOption) error { return fmt.Errorf("cannot create cloud resources: %w", err) } - if i.isVpnConfigured() { - clientName, err := i.generateClientName() - if err != nil { - return err - } - - if err := i.faRunner.ConfigOpenvpnClient(clientName); err != nil { - return fmt.Errorf("error configuring openvpn client: %w", err) - } - - for _, opt := range opts { - if strings.ToLower(opt.Name) == cluster.OperationPhaseOptionVPNAutoConnect { - autoConnect, ok := opt.Value.(bool) - if autoConnect && ok { - connectMsg := "Connecting to VPN" - - isRoot, err := osx.IsRoot() - if err != nil { - return fmt.Errorf("error while checking if user is root: %w", err) - } - - if !isRoot { - connectMsg = fmt.Sprintf("%s, you will be asked for your SUDO password", connectMsg) - } - - logrus.Infof("%s...", connectMsg) - - if err := i.ovRunner.Connect(clientName); err != nil { - return fmt.Errorf("error connecting to VPN: %w", err) - } - } - } - } - - if err := i.copyOpenvpnToWorkDir(); err != nil { - return fmt.Errorf("error copying openvpn file to workdir: %w", err) - } - } - return nil } -func (i *Infrastructure) isVpnConfigured() bool { - vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn - if vpn == nil { - return false - } - - instances := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances - if instances == nil { - return true - } - - return *instances > 0 -} - -func (i *Infrastructure) generateClientName() (string, error) { - whoamiResp, err := exec.Command("whoami").Output() - if err != nil { - return "", fmt.Errorf("error getting current user: %w", err) - } - - whoami := strings.TrimSpace(string(whoamiResp)) - - return fmt.Sprintf("%s-%s", i.furyctlConf.Metadata.Name, whoami), nil -} - func (i *Infrastructure) copyFromTemplate() error { var cfg template.Config @@ -259,37 +186,6 @@ func (i *Infrastructure) copyFromTemplate() error { return nil } -func (i *Infrastructure) copyOpenvpnToWorkDir() error { - currentDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("error getting current dir: %w", err) - } - - ovpnFileName, err := i.generateClientName() - if err != nil { - return err - } - - ovpnFileName = fmt.Sprintf("%s.ovpn", ovpnFileName) - - ovpnPath, err := filepath.Abs(path.Join(i.SecretsPath, ovpnFileName)) - if err != nil { - return fmt.Errorf("error getting ovpn absolute path: %w", err) - } - - ovpnFile, err := os.ReadFile(ovpnPath) - if err != nil { - return fmt.Errorf("error reading ovpn file: %w", err) - } - - err = os.WriteFile(path.Join(currentDir, ovpnFileName), ovpnFile, iox.FullRWPermAccess) - if err != nil { - return fmt.Errorf("error writing ovpn file: %w", err) - } - - return nil -} - func (i *Infrastructure) createTfVars() error { var buffer bytes.Buffer diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 6a9001cb8..750033e2f 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -34,6 +34,7 @@ type ClusterCreator struct { furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD phase string + skipVpn bool vpnAutoConnect bool dryRun bool } @@ -68,6 +69,11 @@ func (v *ClusterCreator) SetProperty(name string, value any) { v.phase = s } + case cluster.CreatorPropertySkipVpn: + if b, ok := value.(bool); ok { + v.skipVpn = b + } + case cluster.CreatorPropertyVpnAutoConnect: if b, ok := value.(bool); ok { v.vpnAutoConnect = b @@ -106,9 +112,15 @@ func (v *ClusterCreator) Create(skipPhase string) error { return err } - infraOpts := []cluster.OperationPhaseOption{ - {Name: cluster.OperationPhaseOptionVPNAutoConnect, Value: v.vpnAutoConnect}, - } + vpnConnector := NewVpnConnector( + v.furyctlConf.Metadata.Name, + infra.SecretsPath, + v.paths.BinPath, + v.kfdManifest.Tools.Common.Furyagent.Version, + v.vpnAutoConnect, + v.skipVpn, + v.furyctlConf.Spec.Infrastructure.Vpc.Vpn, + ) switch v.phase { case cluster.OperationPhaseInfrastructure: @@ -123,7 +135,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return fmt.Errorf("%w: check at %s", ErrInfraNotPresent, absPath) } - if err = infra.Exec(infraOpts); err != nil { + if err = infra.Exec(); err != nil { return fmt.Errorf("error while executing infrastructure phase: %w", err) } @@ -135,15 +147,22 @@ func (v *ClusterCreator) Create(skipPhase string) error { logrus.Info("Infrastructure created successfully") - if v.furyctlConf.Spec.Infrastructure != nil { - if v.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil && v.vpnAutoConnect { - logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") + if vpnConnector.IsConfigured() { + if err = vpnConnector.GenerateCertificates(); err != nil { + return fmt.Errorf("error while generating vpn certificates: %w", err) } } return nil case cluster.OperationPhaseKubernetes: + if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + if err = vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) + } + } + logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + " (e.g. check VPN connection is active`), otherwise the installation will fail.") @@ -171,6 +190,13 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseDistribution: + if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + if err = vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) + } + } + if err = distro.Exec(); err != nil { return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) } @@ -190,7 +216,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseAll: - return v.allPhases(skipPhase, infraOpts, infra, kube, distro) + return v.allPhases(skipPhase, infra, kube, distro, vpnConnector) default: return ErrUnsupportedPhase @@ -199,10 +225,10 @@ func (v *ClusterCreator) Create(skipPhase string) error { func (v *ClusterCreator) allPhases( skipPhase string, - infraOpts []cluster.OperationPhaseOption, infra *create.Infrastructure, kube *create.Kubernetes, distro *create.Distribution, + vpnConnector *VpnConnector, ) error { if v.dryRun { logrus.Info("furcytl will try its best to calculate what would have changed. " + @@ -213,9 +239,23 @@ func (v *ClusterCreator) allPhases( if v.furyctlConf.Spec.Infrastructure != nil && (skipPhase == "" || skipPhase == cluster.OperationPhaseDistribution) { - if err := infra.Exec(infraOpts); err != nil { + if err := infra.Exec(); err != nil { return fmt.Errorf("error while executing infrastructure phase: %w", err) } + + if !v.dryRun && vpnConnector.IsConfigured() { + if err := vpnConnector.GenerateCertificates(); err != nil { + return fmt.Errorf("error while generating vpn certificates: %w", err) + } + } + } + + if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !v.dryRun { + if err := vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) + } } if skipPhase != cluster.OperationPhaseKubernetes { @@ -250,10 +290,8 @@ func (v *ClusterCreator) allPhases( logrus.Info("Kubernetes Fury cluster created successfully") - if v.furyctlConf.Spec.Infrastructure != nil { - if v.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { - logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") - } + if vpnConnector.IsConfigured() { + logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") } err := v.logKubeconfig() diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go new file mode 100644 index 000000000..6c1a944ee --- /dev/null +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -0,0 +1,207 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +import ( + "bufio" + "errors" + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/internal/tool/furyagent" + "github.com/sighupio/furyctl/internal/tool/openvpn" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + osx "github.com/sighupio/furyctl/internal/x/os" +) + +var ( + ErrAutoConnectWithoutVpn = errors.New("autoconnect is not supported without a VPN configuration") + ErrReadStdin = errors.New("error reading from stdin") +) + +type VpnConnector struct { + clusterName string + certDir string + autoconnect bool + skip bool + config *private.SpecInfrastructureVpcVpn + ovRunner *openvpn.Runner + faRunner *furyagent.Runner +} + +func NewVpnConnector( + clusterName, + certDir, + binPath, + faVersion string, + autoconnect, + skip bool, + config *private.SpecInfrastructureVpcVpn, +) *VpnConnector { + executor := execx.NewStdExecutor() + + return &VpnConnector{ + clusterName: clusterName, + certDir: certDir, + autoconnect: autoconnect, + skip: skip, + config: config, + ovRunner: openvpn.NewRunner(executor, openvpn.Paths{ + WorkDir: certDir, + Openvpn: "openvpn", + }), + faRunner: furyagent.NewRunner(executor, furyagent.Paths{ + Furyagent: path.Join(binPath, "furyagent", faVersion, "furyagent"), + WorkDir: certDir, + }), + } +} + +func (v *VpnConnector) Connect() error { + if v.autoconnect { + if !v.IsConfigured() { + return ErrAutoConnectWithoutVpn + } + + return v.startOpenVPN() + } + + if !v.skip { + return v.prompt() + } + + return nil +} + +func (v *VpnConnector) GenerateCertificates() error { + clientName, err := v.ClientName() + if err != nil { + return err + } + + if err := v.faRunner.ConfigOpenvpnClient(clientName); err != nil { + return fmt.Errorf("error configuring openvpn client: %w", err) + } + + if err := v.copyOpenvpnToWorkDir(clientName); err != nil { + return fmt.Errorf("error copying openvpn file to workdir: %w", err) + } + + return nil +} + +func (v *VpnConnector) IsConfigured() bool { + vpn := v.config + if vpn == nil { + return false + } + + instances := v.config.Instances + if instances == nil { + return true + } + + return *instances > 0 +} + +func (v *VpnConnector) ClientName() (string, error) { + whoamiResp, err := exec.Command("whoami").Output() + if err != nil { + return "", fmt.Errorf("error getting current user: %w", err) + } + + whoami := strings.TrimSpace(string(whoamiResp)) + + return fmt.Sprintf("%s-%s", v.clusterName, whoami), nil +} + +func (v *VpnConnector) copyOpenvpnToWorkDir(clientName string) error { + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current dir: %w", err) + } + + ovpnFileName := fmt.Sprintf("%s.ovpn", clientName) + + ovpnPath, err := filepath.Abs(path.Join(v.certDir, ovpnFileName)) + if err != nil { + return fmt.Errorf("error getting ovpn absolute path: %w", err) + } + + ovpnFile, err := os.ReadFile(ovpnPath) + if err != nil { + return fmt.Errorf("error reading ovpn file: %w", err) + } + + err = os.WriteFile(path.Join(currentDir, ovpnFileName), ovpnFile, iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing ovpn file: %w", err) + } + + return nil +} + +func (v *VpnConnector) startOpenVPN() error { + connectMsg := "Connecting to VPN" + + isRoot, err := osx.IsRoot() + if err != nil { + return fmt.Errorf("error while checking if user is root: %w", err) + } + + clientName, err := v.ClientName() + if err != nil { + return fmt.Errorf("error getting client name: %w", err) + } + + if !isRoot { + connectMsg = fmt.Sprintf("%s, you will be asked for your SUDO password", connectMsg) + } + + logrus.Infof("%s...", connectMsg) + + if err := v.ovRunner.Connect(clientName); err != nil { + return fmt.Errorf("error connecting to VPN: %w", err) + } + + return nil +} + +func (v *VpnConnector) prompt() error { + connectMsg := "Please connect to the VPN before continuing" + + clientName, err := v.ClientName() + if err != nil { + return fmt.Errorf("error getting client name: %w", err) + } + + certPath := filepath.Join(v.certDir, clientName) + + if v.IsConfigured() { + connectMsg = fmt.Sprintf( + "%s, you can find the configuration file in %s.\nPress enter to continue", + connectMsg, + certPath, + ) + } + + logrus.Info(connectMsg) + + logrus.Info("Press enter to continue...") + + if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil { + return fmt.Errorf("%w: %v", ErrReadStdin, err) + } + + return nil +} diff --git a/internal/cluster/creator.go b/internal/cluster/creator.go index 33811d60b..2ee475a1c 100644 --- a/internal/cluster/creator.go +++ b/internal/cluster/creator.go @@ -21,6 +21,7 @@ const ( CreatorPropertyDistroPath = "distropath" CreatorPropertyBinPath = "binpath" CreatorPropertyPhase = "phase" + CreatorPropertySkipVpn = "skipvpn" CreatorPropertyVpnAutoConnect = "vpnautoconnect" CreatorPropertyKubeconfig = "kubeconfig" CreatorPropertyDryRun = "dryrun" @@ -58,7 +59,8 @@ func NewCreator( kfdManifest config.KFD, paths CreatorPaths, phase string, - vpnAutoConnect bool, + skipVpn, + vpnAutoConnect, dryRun bool, ) (Creator, error) { lcAPIVersion := strings.ToLower(minimalConf.APIVersion) @@ -74,6 +76,10 @@ func NewCreator( Name: CreatorPropertyPhase, Value: phase, }, + { + Name: CreatorPropertySkipVpn, + Value: skipVpn, + }, { Name: CreatorPropertyVpnAutoConnect, Value: vpnAutoConnect, From f3b7e3ce042f679549b5182e6b61c76019e10ea7 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 3 Mar 2023 11:09:15 +0100 Subject: [PATCH 158/383] feat: added also to delete cmd --- cmd/delete/cluster.go | 49 ++++++++++++++-- go.mod | 11 +++- go.sum | 24 +++++++- internal/apis/kfd/v1alpha2/eks/creator.go | 48 ++++++++++----- internal/apis/kfd/v1alpha2/eks/deleter.go | 71 ++++++++++++++++++----- internal/apis/kfd/v1alpha2/eks/vpn.go | 63 +++++++++++++++++++- internal/cluster/deleter.go | 48 ++++++++++----- 7 files changed, 260 insertions(+), 54 deletions(-) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 3bd20d2b6..d83c05dfd 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -38,6 +38,8 @@ type ClusterCmdFlags struct { Phase string BinPath string Force bool + SkipVpn bool + VpnAutoConnect bool DryRun bool NoTTY bool Kubeconfig string @@ -129,14 +131,21 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while validating dependencies: %w", err) } + // Define cluster deletion paths. + paths := cluster.DeleterPaths{ + ConfigPath: flags.FuryctlPath, + WorkDir: basePath, + BinPath: flags.BinPath, + Kubeconfig: kubeconfigPath, + } + clusterDeleter, err := cluster.NewDeleter( res.MinimalConf, res.DistroManifest, - flags.FuryctlPath, + paths, flags.Phase, - basePath, - flags.BinPath, - kubeconfigPath, + flags.SkipVpn, + flags.VpnAutoConnect, flags.DryRun, ) if err != nil { @@ -220,6 +229,18 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "when set furyctl won't delete any resources. Allows to inspect what resources will be deleted", ) + cmd.Flags().Bool( + "vpn-auto-connect", + false, + "When set will automatically connect to the created VPN in the infrastructure phase", + ) + + cmd.Flags().Bool( + "skip-vpn", + false, + "When set will not wait for user confirmation to connect to the VPN", + ) + cmd.Flags().Bool( "force", false, @@ -264,6 +285,24 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm binPath := cmdutil.StringFlagOptional(cmd, "bin-path") + skipVpn, err := cmdutil.BoolFlag(cmd, "skip-vpn", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-vpn") + } + + vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-auto-connect") + } + + if skipVpn && vpnAutoConnect { + return ClusterCmdFlags{}, fmt.Errorf( + "%w: %s: cannot use together with skip-vpn flag", + ErrParsingFlag, + "vpn-auto-connect", + ) + } + dryRun, err := cmdutil.BoolFlag(cmd, "dry-run", tracker, cmdEvent) if err != nil { return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "dry-run") @@ -290,6 +329,8 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm DistroLocation: distroLocation, Phase: phase, BinPath: binPath, + SkipVpn: skipVpn, + VpnAutoConnect: vpnAutoConnect, DryRun: dryRun, Force: force, NoTTY: noTTY, diff --git a/go.mod b/go.mod index 921922589..7629c868c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 gopkg.in/yaml.v2 v2.4.0 ) @@ -26,6 +26,7 @@ require ( github.com/onsi/ginkgo/v2 v2.6.1 github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 + github.com/shirou/gopsutil/v3 v3.23.2 github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 @@ -45,6 +46,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -62,6 +64,7 @@ require ( github.com/jsonmaur/aws-regions/v2 v2.3.1 // indirect github.com/klauspost/compress v1.15.13 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect @@ -73,20 +76,24 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.1 // indirect + github.com/tklauser/go-sysconf v0.3.11 // indirect + github.com/tklauser/numcpus v0.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/zclconf/go-cty v1.12.1 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.4.0 // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.4.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect - golang.org/x/sys v0.3.0 // indirect + golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/tools v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index fe2d7585a..148d15bb9 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -154,6 +156,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -236,6 +239,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -279,6 +284,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -289,6 +296,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= +github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU= +github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -320,10 +329,15 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -336,6 +350,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= @@ -475,6 +491,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -496,6 +513,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -514,8 +532,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 750033e2f..bd2d1c21b 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -156,8 +156,9 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: - if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -182,16 +183,20 @@ func (v *ClusterCreator) Create(skipPhase string) error { logrus.Info("Kubernetes cluster created successfully") - err = v.logKubeconfig() - if err != nil { + if err := v.logKubeconfig(); err != nil { return fmt.Errorf("error while logging kubeconfig path: %w", err) } + if err := v.logVPNKill(vpnConnector); err != nil { + return fmt.Errorf("error while logging vpn kill message: %w", err) + } + return nil case cluster.OperationPhaseDistribution: - if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -213,6 +218,10 @@ func (v *ClusterCreator) Create(skipPhase string) error { logrus.Info("Kubernetes Fury Distribution installed successfully") + if err := v.logVPNKill(vpnConnector); err != nil { + return fmt.Errorf("error while logging vpn kill message: %w", err) + } + return nil case cluster.OperationPhaseAll: @@ -250,9 +259,10 @@ func (v *ClusterCreator) allPhases( } } - if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !v.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -290,12 +300,11 @@ func (v *ClusterCreator) allPhases( logrus.Info("Kubernetes Fury cluster created successfully") - if vpnConnector.IsConfigured() { - logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") + if err := v.logVPNKill(vpnConnector); err != nil { + return fmt.Errorf("error while logging vpn kill message: %w", err) } - err := v.logKubeconfig() - if err != nil { + if err := v.logKubeconfig(); err != nil { return fmt.Errorf("error while logging kubeconfig path: %w", err) } @@ -348,6 +357,19 @@ func (*ClusterCreator) logKubeconfig() error { return nil } +func (*ClusterCreator) logVPNKill(vpnConnector *VpnConnector) error { + if vpnConnector.IsConfigured() { + killVpnMsg, err := vpnConnector.GetKillMessage() + if err != nil { + return err + } + + logrus.Info(killVpnMsg) + } + + return nil +} + func (v *ClusterCreator) storeClusterConfig() error { x, err := os.ReadFile(v.paths.ConfigPath) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 3a9f49be7..11ea76350 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -17,13 +17,13 @@ import ( ) type ClusterDeleter struct { - kfdManifest config.KFD - furyctlConf private.EksclusterKfdV1Alpha2 - phase string - workDir string - binPath string - kubeconfig string - dryRun bool + paths cluster.DeleterPaths + kfdManifest config.KFD + furyctlConf private.EksclusterKfdV1Alpha2 + phase string + skipVpn bool + vpnAutoConnect bool + dryRun bool } func (d *ClusterDeleter) SetProperties(props []cluster.DeleterProperty) { @@ -51,19 +51,29 @@ func (d *ClusterDeleter) SetProperty(name string, value any) { d.phase = s } + case cluster.DeleterPropertySkipVpn: + if b, ok := value.(bool); ok { + d.skipVpn = b + } + + case cluster.DeleterPropertyVpnAutoConnect: + if b, ok := value.(bool); ok { + d.vpnAutoConnect = b + } + case cluster.DeleterPropertyWorkDir: if s, ok := value.(string); ok { - d.workDir = s + d.paths.WorkDir = s } case cluster.DeleterPropertyBinPath: if s, ok := value.(string); ok { - d.binPath = s + d.paths.BinPath = s } case cluster.DeleterPropertyKubeconfig: if s, ok := value.(string); ok { - d.kubeconfig = s + d.paths.Kubeconfig = s } case cluster.DeleterPropertyDryRun: @@ -74,21 +84,31 @@ func (d *ClusterDeleter) SetProperty(name string, value any) { } func (d *ClusterDeleter) Delete() error { - distro, err := del.NewDistribution(d.dryRun, d.workDir, d.binPath, d.kfdManifest, d.kubeconfig) + distro, err := del.NewDistribution(d.dryRun, d.paths.WorkDir, d.paths.BinPath, d.kfdManifest, d.paths.Kubeconfig) if err != nil { return fmt.Errorf("error while creating distribution phase: %w", err) } - kube, err := del.NewKubernetes(d.furyctlConf, d.dryRun, d.workDir, d.binPath, d.kfdManifest) + kube, err := del.NewKubernetes(d.furyctlConf, d.dryRun, d.paths.WorkDir, d.paths.BinPath, d.kfdManifest) if err != nil { return fmt.Errorf("error while creating kubernetes phase: %w", err) } - infra, err := del.NewInfrastructure(d.furyctlConf, d.dryRun, d.workDir, d.binPath, d.kfdManifest) + infra, err := del.NewInfrastructure(d.furyctlConf, d.dryRun, d.paths.WorkDir, d.paths.BinPath, d.kfdManifest) if err != nil { return fmt.Errorf("error while creating infrastructure phase: %w", err) } + vpnConnector := NewVpnConnector( + d.furyctlConf.Metadata.Name, + infra.SecretsPath, + d.paths.BinPath, + d.kfdManifest.Tools.Common.Furyagent.Version, + d.vpnAutoConnect, + d.skipVpn, + d.furyctlConf.Spec.Infrastructure.Vpc.Vpn, + ) + switch d.phase { case cluster.OperationPhaseInfrastructure: if err := infra.Exec(); err != nil { @@ -100,6 +120,14 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseKubernetes: + if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + if err = vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) + } + } + logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + " (e.g. check VPN connection is active`), otherwise the deletion will fail.") @@ -112,6 +140,14 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseDistribution: + if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + if err = vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) + } + } + if err := distro.Exec(); err != nil { return fmt.Errorf("error while deleting distribution phase: %w", err) } @@ -126,6 +162,15 @@ func (d *ClusterDeleter) Delete() error { "Sometimes this is not possible, for better results limit the scope with the --phase flag.") } + if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !d.dryRun { + if err := vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) + } + } + if err := distro.Exec(); err != nil { return fmt.Errorf("error while deleting distribution phase: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 6c1a944ee..50c150afd 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strings" + "github.com/shirou/gopsutil/v3/process" "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/schema/private" @@ -27,6 +28,7 @@ import ( var ( ErrAutoConnectWithoutVpn = errors.New("autoconnect is not supported without a VPN configuration") ErrReadStdin = errors.New("error reading from stdin") + ErrOpenvpnRunning = errors.New("an openvpn process is already running, please kill it and try again") ) type VpnConnector struct { @@ -73,6 +75,10 @@ func (v *VpnConnector) Connect() error { return ErrAutoConnectWithoutVpn } + if err := v.checkExistingOpenVPN(); err != nil { + return err + } + return v.startOpenVPN() } @@ -125,6 +131,27 @@ func (v *VpnConnector) ClientName() (string, error) { return fmt.Sprintf("%s-%s", v.clusterName, whoami), nil } +func (v *VpnConnector) GetKillMessage() (string, error) { + endVpnMsg := "Please remember to kill the VPN connection when you finish doing operations on the cluster" + + if !v.autoconnect { + return endVpnMsg, nil + } + + killMsg := "killall openvpn" + + isRoot, err := osx.IsRoot() + if err != nil { + return "", fmt.Errorf("error while checking if user is root: %w", err) + } + + if !isRoot { + killMsg = fmt.Sprintf("sudo %s", killMsg) + } + + return fmt.Sprintf("%s, you can do it with the following command: '%s'", endVpnMsg, killMsg), nil +} + func (v *VpnConnector) copyOpenvpnToWorkDir(clientName string) error { currentDir, err := os.Getwd() if err != nil { @@ -151,6 +178,23 @@ func (v *VpnConnector) copyOpenvpnToWorkDir(clientName string) error { return nil } +func (*VpnConnector) checkExistingOpenVPN() error { + processes, err := process.Processes() + if err != nil { + return fmt.Errorf("error getting processes: %w", err) + } + + for _, p := range processes { + name, _ := p.Name() //nolint:errcheck // we don't care about the error here + + if name == "openvpn" { + return ErrOpenvpnRunning + } + } + + return nil +} + func (v *VpnConnector) startOpenVPN() error { connectMsg := "Connecting to VPN" @@ -185,19 +229,32 @@ func (v *VpnConnector) prompt() error { return fmt.Errorf("error getting client name: %w", err) } - certPath := filepath.Join(v.certDir, clientName) + certPath := filepath.Join(v.certDir, fmt.Sprintf("%s.ovpn", clientName)) if v.IsConfigured() { + isRoot, err := osx.IsRoot() + if err != nil { + return fmt.Errorf("error while checking if user is root: %w", err) + } + + vpnConnectCmd := fmt.Sprintf("openvpn --config %s --daemon", certPath) + + if !isRoot { + vpnConnectCmd = fmt.Sprintf("sudo %s", vpnConnectCmd) + } + connectMsg = fmt.Sprintf( - "%s, you can find the configuration file in %s.\nPress enter to continue", + "%s, you can find the configuration file in %s and connect to the VPN by running the command "+ + "'%s' or using your VPN client of choice.", connectMsg, certPath, + vpnConnectCmd, ) } logrus.Info(connectMsg) - logrus.Info("Press enter to continue...") + logrus.Info("Press enter when you are ready to continue...") if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil { return fmt.Errorf("%w: %v", ErrReadStdin, err) diff --git a/internal/cluster/deleter.go b/internal/cluster/deleter.go index 85fda052e..597f6675a 100644 --- a/internal/cluster/deleter.go +++ b/internal/cluster/deleter.go @@ -13,18 +13,27 @@ import ( ) const ( - DeleterPropertyFuryctlConf = "furyctlconf" - DeleterPropertyPhase = "phase" - DeleterPropertyWorkDir = "workdir" - DeleterPropertyKfdManifest = "kfdmanifest" - DeleterPropertyBinPath = "binpath" - DeleterPropertyKubeconfig = "kubeconfig" - DeleterPropertyDryRun = "dryrun" + DeleterPropertyFuryctlConf = "furyctlconf" + DeleterPropertyPhase = "phase" + DeleterPropertyWorkDir = "workdir" + DeleterPropertyKfdManifest = "kfdmanifest" + DeleterPropertyBinPath = "binpath" + DeleterPropertySkipVpn = "skipvpn" + DeleterPropertyVpnAutoConnect = "vpnautoconnect" + DeleterPropertyKubeconfig = "kubeconfig" + DeleterPropertyDryRun = "dryrun" ) var delFactories = make(map[string]map[string]DeleterFactory) //nolint:gochecknoglobals, lll // This patterns requires factories // as global to work with init function. +type DeleterPaths struct { + ConfigPath string + WorkDir string + BinPath string + Kubeconfig string +} + type DeleterFactory func(configPath string, props []DeleterProperty) (Deleter, error) type DeleterProperty struct { @@ -41,18 +50,17 @@ type Deleter interface { func NewDeleter( minimalConf config.Furyctl, kfdManifest config.KFD, - configPath, - phase, - workDir, - binPath, - kubeconfig string, + paths DeleterPaths, + phase string, + skipVpn, + vpnAutoConnect, dryRun bool, ) (Deleter, error) { lcAPIVersion := strings.ToLower(minimalConf.APIVersion) lcResourceType := strings.ToLower(minimalConf.Kind) if factoryFn, ok := delFactories[lcAPIVersion][lcResourceType]; ok { - return factoryFn(configPath, []DeleterProperty{ + return factoryFn(paths.ConfigPath, []DeleterProperty{ { Name: DeleterPropertyKfdManifest, Value: kfdManifest, @@ -63,15 +71,23 @@ func NewDeleter( }, { Name: DeleterPropertyWorkDir, - Value: workDir, + Value: paths.WorkDir, }, { Name: DeleterPropertyBinPath, - Value: binPath, + Value: paths.BinPath, }, { Name: DeleterPropertyKubeconfig, - Value: kubeconfig, + Value: paths.Kubeconfig, + }, + { + Name: DeleterPropertySkipVpn, + Value: skipVpn, + }, + { + Name: DeleterPropertyVpnAutoConnect, + Value: vpnAutoConnect, }, { Name: DeleterPropertyDryRun, From b52041bf04cbc5e4c5a1cac4d83462501052a416 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 3 Mar 2023 11:33:16 +0100 Subject: [PATCH 159/383] fix: exclude dry-run --- internal/apis/kfd/v1alpha2/eks/creator.go | 6 ++++-- internal/apis/kfd/v1alpha2/eks/deleter.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index bd2d1c21b..3d739e2ed 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -158,7 +158,8 @@ func (v *ClusterCreator) Create(skipPhase string) error { case cluster.OperationPhaseKubernetes: if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -196,7 +197,8 @@ func (v *ClusterCreator) Create(skipPhase string) error { case cluster.OperationPhaseDistribution: if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 11ea76350..a47445984 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -122,7 +122,8 @@ func (d *ClusterDeleter) Delete() error { case cluster.OperationPhaseKubernetes: if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -142,7 +143,8 @@ func (d *ClusterDeleter) Delete() error { case cluster.OperationPhaseDistribution: if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate && + !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 0d39814811fa1cd27c9ec076b1fd21f7648c6c21 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 3 Mar 2023 14:25:08 +0100 Subject: [PATCH 160/383] fix: wrong condition --- internal/apis/kfd/v1alpha2/eks/creator.go | 18 +++++++++--------- internal/apis/kfd/v1alpha2/eks/deleter.go | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 3d739e2ed..971749f25 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -156,10 +156,10 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: - if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + if (v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !v.dryRun { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && + !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -195,10 +195,10 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseDistribution: - if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + if (v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !v.dryRun { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && + !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -261,10 +261,10 @@ func (v *ClusterCreator) allPhases( } } - if v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + if (v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !v.dryRun { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && + !v.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index a47445984..429c63cde 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -120,10 +120,10 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseKubernetes: - if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + if (d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !d.dryRun { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && + !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -141,10 +141,10 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseDistribution: - if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + if (d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !d.dryRun { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && + !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -164,10 +164,10 @@ func (d *ClusterDeleter) Delete() error { "Sometimes this is not possible, for better results limit the scope with the --phase flag.") } - if d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || + if (d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate && - !d.dryRun { + private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && + !d.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 04089a3091dbbe680e17e80185fb2096f040f8e7 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 6 Mar 2023 15:25:31 +0100 Subject: [PATCH 161/383] feat: removed call to whoami --- internal/apis/kfd/v1alpha2/eks/vpn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 50c150afd..35bd5a1c3 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -9,7 +9,7 @@ import ( "errors" "fmt" "os" - "os/exec" + "os/user" "path" "path/filepath" "strings" @@ -121,12 +121,12 @@ func (v *VpnConnector) IsConfigured() bool { } func (v *VpnConnector) ClientName() (string, error) { - whoamiResp, err := exec.Command("whoami").Output() + u, err := user.Current() if err != nil { return "", fmt.Errorf("error getting current user: %w", err) } - whoami := strings.TrimSpace(string(whoamiResp)) + whoami := strings.TrimSpace(u.Username) return fmt.Sprintf("%s-%s", v.clusterName, whoami), nil } From e2682b97260b949bea5c12f208715fe2f8d9a76a Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 6 Mar 2023 15:26:06 +0100 Subject: [PATCH 162/383] chore: renamed skip vpn flags --- cmd/create/cluster.go | 6 +++--- cmd/delete/cluster.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 2ef86976b..7455b0871 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -266,9 +266,9 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm binPath := cmdutil.StringFlagOptional(cmd, "bin-path") - skipVpn, err := cmdutil.BoolFlag(cmd, "skip-vpn", tracker, cmdEvent) + skipVpn, err := cmdutil.BoolFlag(cmd, "vpn-skip", tracker, cmdEvent) if err != nil { - return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-vpn") + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-skip") } vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) @@ -392,7 +392,7 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { ) cmd.Flags().Bool( - "skip-vpn", + "vpn-skip", false, "When set will not wait for user confirmation to connect to the VPN", ) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index d83c05dfd..dafd88d29 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -236,7 +236,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { ) cmd.Flags().Bool( - "skip-vpn", + "vpn-skip", false, "When set will not wait for user confirmation to connect to the VPN", ) @@ -285,9 +285,9 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm binPath := cmdutil.StringFlagOptional(cmd, "bin-path") - skipVpn, err := cmdutil.BoolFlag(cmd, "skip-vpn", tracker, cmdEvent) + skipVpn, err := cmdutil.BoolFlag(cmd, "vpn-skip", tracker, cmdEvent) if err != nil { - return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-vpn") + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-skip") } vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) From f61fd1af73a9c6ba63ed747d0b9d62b2fcd92614 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 8 Mar 2023 18:54:35 +0100 Subject: [PATCH 163/383] feat: added legacy vendor cmd --- cmd/legacy.go | 23 ++ cmd/legacy/vendor.go | 139 ++++++++++++ cmd/root.go | 1 + internal/legacy/dir.go | 33 +++ internal/legacy/download.go | 364 +++++++++++++++++++++++++++++++ internal/legacy/furyfile.go | 121 ++++++++++ internal/legacy/package_url.go | 73 +++++++ internal/legacy/provider_kind.go | 43 ++++ 8 files changed, 797 insertions(+) create mode 100644 cmd/legacy.go create mode 100644 cmd/legacy/vendor.go create mode 100644 internal/legacy/dir.go create mode 100644 internal/legacy/download.go create mode 100644 internal/legacy/furyfile.go create mode 100644 internal/legacy/package_url.go create mode 100644 internal/legacy/provider_kind.go diff --git a/cmd/legacy.go b/cmd/legacy.go new file mode 100644 index 000000000..bdd4f18c2 --- /dev/null +++ b/cmd/legacy.go @@ -0,0 +1,23 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/cmd/legacy" + "github.com/sighupio/furyctl/internal/analytics" +) + +func NewLegacyCommand(tracker *analytics.Tracker) *cobra.Command { + legacyCmd := &cobra.Command{ + Use: "legacy", + Short: "Legacy commands for compatibility with older versions of furyctl", + } + + legacyCmd.AddCommand(legacy.NewVendorCmd(tracker)) + + return legacyCmd +} diff --git a/cmd/legacy/vendor.go b/cmd/legacy/vendor.go new file mode 100644 index 000000000..7de7c5004 --- /dev/null +++ b/cmd/legacy/vendor.go @@ -0,0 +1,139 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacy + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/legacy" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" +) + +var ( + ErrParsingFlag = errors.New("error while parsing flag") + ErrParsingFuryFile = errors.New("error while parsing furyfile") + ErrParsingPackages = errors.New("error while parsing packages") + ErrDownloading = errors.New("error while downloading") +) + +type VendorCmdFlags struct { + FuryFilePath string + Prefix string + HTTPS bool +} + +func NewVendorCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + + cmd := &cobra.Command{ + Use: "vendor", + Short: "Download the dependencies specified in the Furyfile.yml", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + flags, err := getLegacyVendorCmdFlags(cmd, tracker, cmdEvent) + if err != nil { + return err + } + + ff, err := legacy.NewFuryFile(flags.FuryFilePath) + if err != nil { + return fmt.Errorf("%w: %v", ErrParsingFuryFile, err) + } + + ps, err := ff.BuildPackages(flags.Prefix) + if err != nil { + return fmt.Errorf("%w: %v", ErrParsingPackages, err) + } + + if token := os.Getenv("GITHUB_TOKEN"); strings.Contains(token, " ") { + logrus.Warn("GITHUB_TOKEN contains a space character. As a result, " + + "vendoring modules may fail. If it's intended, you can ignore this warning.\n") + } + + for _, p := range ps { + if p.Version == "" { + logrus.Warnf( + "package '%s' has no version specified. Will download the default git branch", + p.Name, + ) + } else { + logrus.Infof("using version '%v' for package '%s'", p.Version, p.Name) + } + } + + downloader := legacy.NewDownloader(flags.HTTPS) + + err = downloader.Download(ps) + if err != nil { + return fmt.Errorf("%w: %v", ErrDownloading, err) + } + + cmdEvent.AddSuccessMessage("dependencies downloaded successfully") + tracker.Track(cmdEvent) + + return nil + }, + } + + cmd.Flags().StringP( + "furyfile", + "F", + "Furyfile.yml", + "Path to the Furyfile.yml file", + ) + + cmd.Flags().BoolP( + "https", + "H", + false, + "download using HTTPS instead of SSH protocol. Use when SSH traffic is being blocked or when SSH "+ + "client has not been configured\nset the GITHUB_TOKEN environment variable with your token to use "+ + "authentication while downloading, for example for private repositories", + ) + + cmd.Flags().StringP( + "prefix", + "P", + "", + "download modules that start with prefix only to reduce download scope. "+ + "Example:\nfuryctl legacy vendor -P mon\nwill download all modules that start with 'mon', "+ + "like 'monitoring', and ignore the rest", + ) + + return cmd +} + +func getLegacyVendorCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (VendorCmdFlags, error) { + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return VendorCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "https") + } + + prefix, err := cmdutil.StringFlag(cmd, "prefix", tracker, cmdEvent) + if err != nil { + return VendorCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "prefix") + } + + furyFilePath, err := cmdutil.StringFlag(cmd, "furyfile", tracker, cmdEvent) + if err != nil { + return VendorCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "furyfile") + } + + return VendorCmdFlags{ + FuryFilePath: furyFilePath, + Prefix: prefix, + HTTPS: https, + }, nil +} diff --git a/cmd/root.go b/cmd/root.go index c9c8b191c..602d01aa5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -207,6 +207,7 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber rootCmd.AddCommand(NewValidateCommand(tracker)) rootCmd.AddCommand(NewVersionCmd(versions, tracker)) rootCmd.AddCommand(NewDeleteCommand(tracker)) + rootCmd.AddCommand(NewLegacyCommand(tracker)) return rootCmd } diff --git a/internal/legacy/dir.go b/internal/legacy/dir.go new file mode 100644 index 000000000..e11a8be9d --- /dev/null +++ b/internal/legacy/dir.go @@ -0,0 +1,33 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacy + +import "fmt" + +type DirSpec struct { + VendorFolder string + Kind string + Name string + Registry bool + Provider ProviderOptSpec +} + +func newDir(vendorFolder string, pkg Package) *DirSpec { + return &DirSpec{ + VendorFolder: vendorFolder, + Kind: pkg.Kind, + Name: pkg.Name, + Registry: pkg.Registry, + Provider: pkg.ProviderOpt, + } +} + +func (d *DirSpec) getConsumableDirectory() string { + if d.Registry { + return fmt.Sprintf("%s/%s/%s/%s/%s", d.VendorFolder, d.Kind, d.Provider.Label, d.Provider.Name, d.Name) + } + + return fmt.Sprintf("%s/%s/%s", d.VendorFolder, d.Kind, d.Name) +} diff --git a/internal/legacy/download.go b/internal/legacy/download.go new file mode 100644 index 000000000..6e1b79b9e --- /dev/null +++ b/internal/legacy/download.go @@ -0,0 +1,364 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacy + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "strings" + "sync" + + "github.com/hashicorp/go-getter" + "github.com/sirupsen/logrus" +) + +const ( + httpsRepoPrefix = "git::https://github.com/sighupio/fury-kubernetes" + sshRepoPrefix = "git@github.com:sighupio/fury-kubernetes" + fallbackHTTPSRepoPrefix = "git::https://github.com/sighupio/kubernetes-fury" + fallbackSSHRepoPrefix = "git@github.com:sighupio/kubernetes-fury" +) + +var ( + ErrDownloadRepo = errors.New("error downloading repository") + ErrSomeDownloadsFailed = errors.New("some downloads have failed. Please check the logs") + ErrRemoveDir = errors.New("error removing directory") + ErrRenameDir = errors.New("error renaming directory") + ErrGettingWD = errors.New("error getting working directory") + ErrGETRequest = errors.New("error performing GET request") +) + +type Downloader struct { + HTTPS bool +} + +func NewDownloader(https bool) Downloader { + return Downloader{ + HTTPS: https, + } +} + +func (d *Downloader) Download(packages []Package) error { + var wg sync.WaitGroup + + errChan := make(chan error, len(packages)) + jobs := make(chan Package, len(packages)) + + for _, p := range packages { + jobs <- p + } + + logrus.Debugf("workers = %d", len(jobs)) + + for i, data := range packages { + wg.Add(1) + + go d.downloadProcess(&wg, data, errChan, i) + + logrus.Debugf("created worker %d", i) + } + + wg.Wait() + + close(jobs) + + close(errChan) + + logrus.Debugf("finished downloading all packages") + + if len(errChan) > 0 { + for err := range errChan { + if err != nil { + errString := strings.ReplaceAll(err.Error(), "\n", " ") + logrus.Errorln(errString) + } + } + + return ErrSomeDownloadsFailed + } + + return nil +} + +func (d *Downloader) downloadProcess(wg *sync.WaitGroup, data Package, errChan chan<- error, i int) { + var pU *PackageURL + + var url string + + defer wg.Done() + + logrus.Debugf("worker %d : received data %v", i, data) + + if d.HTTPS { + pU = newPackageURL( + httpsRepoPrefix, + strings.Split(data.Name, "/"), + data.Kind, + data.Version, + data.Registry, + data.ProviderOpt, + data.ProviderKind) + + resp, err := checkRepository(pU) + if err != nil { + errChan <- err + + return + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusNotFound { + o := humanReadableSource(pU.getConsumableURL()) + + pU.Prefix = fallbackHTTPSRepoPrefix + + logrus.Infof( + "downloading '%s' failed, falling back to '%s' and retrying", + o, + humanReadableSource(pU.getConsumableURL()), + ) + + if resp, err := checkRepository(pU); err != nil || + resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusNotFound { + errChan <- fmt.Errorf( + "%w: error downloading %s for '%s' version '%s'. Both urls '%s' and '%s' have failed."+ + " Please check that the repository exists and that your credentials are"+ + " correctlly configured", + err, + data.Kind, + data.Name, + data.Version, + o, + humanReadableSource(pU.getConsumableURL()), + ) + + defer resp.Body.Close() + + return + } + } + + url = pU.getConsumableURL() + if token := os.Getenv("GITHUB_TOKEN"); token != "" && d.HTTPS { + url = normalizeURLWithToken(pU.getConsumableURL()) + } + } + + if !d.HTTPS { + pU = newPackageURL( + sshRepoPrefix, + strings.Split(data.Name, "/"), + data.Kind, + data.Version, + data.Registry, + data.ProviderOpt, + data.ProviderKind) + + url = pU.getConsumableURL() + + if err := get(url, data.Dir, getter.ClientModeDir); err != nil { + o := humanReadableSource(pU.getConsumableURL()) + + pU.Prefix = fallbackSSHRepoPrefix + + logrus.Infof( + "downloading '%s' failed, falling back to %s and retrying", + o, + humanReadableSource(pU.getConsumableURL()), + ) + + url = pU.getConsumableURL() + + if err := get(url, data.Dir, getter.ClientModeDir); err != nil { + errChan <- fmt.Errorf( + "%w: error downloading %s for '%s' version '%s'. Both urls '%s' and '%s' have failed."+ + " Please check that the repository exists and that your credentials are"+ + " correctlly configured. You might want to try using the -H flag", + err, + data.Kind, + data.Name, + data.Version, + o, + humanReadableSource(pU.getConsumableURL()), + ) + + return + } + } + } + + downloadErr := get(url, data.Dir, getter.ClientModeDir) + if downloadErr != nil { + if err := os.RemoveAll(data.Dir); err != nil { + logrus.Errorf("error removing directory '%s': %s", data.Dir, err.Error()) + } + errChan <- downloadErr + } +} + +func humanReadableSource(src string) string { + humanReadableSrc := src + + if strings.Count(src, "@") >= 1 { + humanReadableSrc = strings.Join(strings.Split(src, ":")[1:], ":") + humanReadableSrc = strings.Replace(humanReadableSrc, "//", "/", 1) + } + + if strings.Count(humanReadableSrc, "//") >= 1 { + humanReadableSrc = strings.Join(strings.Split(humanReadableSrc, "//")[1:], "/") + } + + return humanReadableSrc +} + +func get(src, dest string, mode getter.ClientMode) error { + logrus.Debugf("starting download process for '%s' into '%s'", src, dest) + + pwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("%w: %v", ErrGettingWD, err) + } + + client := &getter.Client{ + Src: src, + Dst: dest + ".tmp", + Pwd: pwd, + Mode: mode, + } + + logrus.Debugf("downloading temporary file '%s' into '%s'", client.Src, client.Dst) + + h := humanReadableSource(src) + + logrus.Infof("downloading '%s' into '%s'", h, dest) + + if err := os.RemoveAll(client.Dst); err != nil { + return fmt.Errorf("%w: %v", ErrRemoveDir, err) + } + + if err := client.Get(); err != nil { + return fmt.Errorf("%w: %v", ErrDownloadRepo, err) + } + + if err := renameDir(client.Dst, dest); err != nil { + return fmt.Errorf("%w: %v", ErrRenameDir, err) + } + + gitFolder := fmt.Sprintf("%s/.git", dest) + logrus.Infof("removing git subfolder: %s", gitFolder) + + if err = os.RemoveAll(gitFolder); err != nil { + return fmt.Errorf("%w: %v", ErrRemoveDir, err) + } + + logrus.Debugf("download process finished: %s -> %s", src, dest) + + return nil +} + +func renameDir(src, dest string) error { + if _, err := os.Stat(dest); !os.IsNotExist(err) { + logrus.Infof("removing existing folder: %s", dest) + + err = os.RemoveAll(dest) + if err != nil { + logrus.Error(err) + + return fmt.Errorf("%w: %s", ErrRemoveDir, dest) + } + } + + err := os.Rename(src, dest) + if err != nil { + return fmt.Errorf("%w: %s -> %s", ErrRenameDir, src, dest) + } + + return nil +} + +func normalizeURL(src string) string { + var s string + + if strings.HasPrefix(src, "git@") { + s = strings.Split(src, "//")[0] + s = strings.Replace(s, "git@github.com:", "https://github.com/", 1) + } + + if strings.HasPrefix(src, "git::") { + _, s, _ = strings.Cut(src, "git::") + } + + return strings.Split(s, ".git/")[0] +} + +func normalizeURLWithAPI(src string) string { + var s string + + if strings.HasPrefix(src, "git@") { + s = strings.Split(src, "//")[0] + s = strings.Replace(s, "git@github.com:", "https://api.github.com/repos/", 1) + } + + if strings.HasPrefix(src, "git::") { + s = strings.Replace(src, "git::https://github.com/", "https://api.github.com/repos/", 1) + } + + return strings.Split(s, ".git/")[0] +} + +func normalizeURLWithToken(src string) string { + s := strings.Replace(src, "git::https://", "git::https://oauth2:"+os.Getenv("GITHUB_TOKEN")+"@", 1) + + return s +} + +func checkRepository(pu *PackageURL) (*http.Response, error) { + var url string + + token := os.Getenv("GITHUB_TOKEN") + if token != "" { + url = normalizeURLWithAPI(pu.getConsumableURL()) + } else { + url = normalizeURL(pu.getConsumableURL()) + } + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrGETRequest, err) + } + + req.Header.Set("Accept", "application/vnd.github.v3+json") + + ghToken := os.Getenv("GITHUB_TOKEN") + + if ghToken != "" { + req.Header.Set("Authorization", "Bearer "+ghToken) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrGETRequest, err) + } + + if resp.StatusCode == http.StatusUnauthorized { + url = normalizeURL(pu.getConsumableURL()) + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrGETRequest, err) + } + + resp, err = http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrGETRequest, err) + } + } + + return resp, nil +} diff --git a/internal/legacy/furyfile.go b/internal/legacy/furyfile.go new file mode 100644 index 000000000..12f8f3677 --- /dev/null +++ b/internal/legacy/furyfile.go @@ -0,0 +1,121 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacy + +import ( + "errors" + "fmt" + "strings" + + "github.com/sirupsen/logrus" + + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +var ErrFuryFileUnmarshal = errors.New("error unmarshaling furyfile") + +const ( + defaultVendorFolderName = "vendor" +) + +type VersionPattern map[string]string + +type ProviderOptSpec struct { + Name string `mapstructure:"name"` + Label string `mapstructure:"label"` +} + +type Package struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + URL string `yaml:"url"` + Dir string `yaml:"dir"` + Kind string `yaml:"kind"` + ProviderOpt ProviderOptSpec `mapstructure:"provider"` + ProviderKind ProviderKind `mapstructure:"providerKind"` + Registry bool `mapstructure:"registry"` +} + +type RegistrySpec struct { + BaseURI string `mapstructure:"url"` + Label string `mapstructure:"label"` +} + +type ProviderPattern map[string]ProviderKind + +type FuryFile struct { + VendorFolderName string `yaml:"vendorFolderName"` + Versions VersionPattern `yaml:"versions"` + Roles []Package `yaml:"roles"` + Modules []Package `yaml:"modules"` + Bases []Package `yaml:"bases"` + Provider ProviderPattern `mapstructure:"provider"` +} + +func NewFuryFile(path string) (*FuryFile, error) { + ff, err := yamlx.FromFileV3[FuryFile](path) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrFuryFileUnmarshal, err) + } + + if ff.VendorFolderName == "" { + ff.VendorFolderName = defaultVendorFolderName + } + + return &ff, nil +} + +func (f *FuryFile) BuildPackages(prefix string) ([]Package, error) { + pkgs := make([]Package, 0) + + if prefix != "" { + logrus.Infof("prefix is set to '%s', downloading only matching modules", prefix) + } + + for _, v := range f.Roles { + v.Kind = "roles" + if strings.HasPrefix(v.Name, prefix) { + logrus.Debugf("role '%s' matches prefix, adding it to the download list", v.Name) + pkgs = append(pkgs, v) + } else { + logrus.Debugf("role '%s' does not match prefix, skipping it", v.Name) + } + } + + for _, v := range f.Modules { + v.Kind = "modules" + if strings.HasPrefix(v.Name, prefix) { + logrus.Debugf("module '%s' matches prefix, adding it to the download list", v.Name) + pkgs = append(pkgs, v) + } else { + logrus.Debugf("module '%s' does not match prefix, skipping it", v.Name) + } + } + + for _, v := range f.Bases { + v.Kind = "katalog" + if strings.HasPrefix(v.Name, prefix) { + logrus.Debugf("katalog '%s' matches prefix, adding it to the download list", v.Name) + pkgs = append(pkgs, v) + } else { + logrus.Debugf("katalog '%s' does not match prefix, skipping it", v.Name) + } + } + + for i := range pkgs { + for k, v := range f.Versions { + if strings.HasPrefix(pkgs[i].Name, k) { + pkgs[i].Version = v + + break + } + } + + pkgs[i].ProviderKind = f.Provider[pkgs[i].Kind] + pkgs[i].Dir = newDir(f.VendorFolderName, pkgs[i]).getConsumableDirectory() + } + + return pkgs, nil +} diff --git a/internal/legacy/package_url.go b/internal/legacy/package_url.go new file mode 100644 index 000000000..87b4c78d2 --- /dev/null +++ b/internal/legacy/package_url.go @@ -0,0 +1,73 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacy + +import ( + "fmt" + "path" + "strings" +) + +type PackageURL struct { + Prefix string + Blocks []string + Kind string + Version string + Registry bool + CloudProvider ProviderOptSpec + KindSpec ProviderKind +} + +func newPackageURL( + prefix string, + blocks []string, + kind, + version string, + registry bool, + cloud ProviderOptSpec, + kindSpec ProviderKind, +) *PackageURL { + return &PackageURL{ + Prefix: prefix, + Registry: registry, + Blocks: blocks, + Kind: kind, + Version: version, + CloudProvider: cloud, + KindSpec: kindSpec, + } +} + +func (n *PackageURL) getConsumableURL() string { + if !n.Registry { + return n.getURLFromCompanyRepos() + } + + return fmt.Sprintf("%s/%s%s?ref=%s", n.KindSpec.pickCloudProviderURL(n.CloudProvider), n.Blocks[0], ".git", n.Version) +} + +func (n *PackageURL) getURLFromCompanyRepos() string { + if len(n.Blocks) == 0 { + return "" + } + + dG := "" + + if strings.HasPrefix(n.Prefix, "git::https") { + dG = ".git" + } + + if len(n.Blocks) == 1 { + return fmt.Sprintf("%s-%s%s//%s?ref=%s", n.Prefix, n.Blocks[0], dG, n.Kind, n.Version) + } + + remainingBlocks := "" + + for i := 1; i < len(n.Blocks); i++ { + remainingBlocks = path.Join(remainingBlocks, n.Blocks[i]) + } + + return fmt.Sprintf("%s-%s%s//%s/%s?ref=%s", n.Prefix, n.Blocks[0], dG, n.Kind, remainingBlocks, n.Version) +} diff --git a/internal/legacy/provider_kind.go b/internal/legacy/provider_kind.go new file mode 100644 index 000000000..7b2306a15 --- /dev/null +++ b/internal/legacy/provider_kind.go @@ -0,0 +1,43 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package legacy + +import ( + "errors" + "fmt" + + "github.com/sirupsen/logrus" +) + +var ErrNoLabelFound = errors.New("no label found") + +type ProviderKind map[string][]RegistrySpec + +func (k *ProviderKind) getLabeledURI(providerName, label string) (string, error) { + for name, providerSpecList := range *k { + if name != providerName { + continue + } + + for _, providerMap := range providerSpecList { + if providerMap.Label != label { + continue + } + + return fmt.Sprintf("git::%s", providerMap.BaseURI), nil + } + } + + return "", fmt.Errorf("%w: %s", ErrNoLabelFound, label) +} + +func (k *ProviderKind) pickCloudProviderURL(cloudProvider ProviderOptSpec) string { + url, err := k.getLabeledURI(cloudProvider.Name, cloudProvider.Label) + if err != nil { + logrus.Fatal(err) + } + + return url +} From b1f7299ac02b30ec6d02bf61d8bd5d4ddd358927 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:32:58 +0100 Subject: [PATCH 164/383] fix: changed tags to yaml instead of mapstructure (#328) --- internal/legacy/furyfile.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/legacy/furyfile.go b/internal/legacy/furyfile.go index 12f8f3677..aaaad17b6 100644 --- a/internal/legacy/furyfile.go +++ b/internal/legacy/furyfile.go @@ -23,8 +23,8 @@ const ( type VersionPattern map[string]string type ProviderOptSpec struct { - Name string `mapstructure:"name"` - Label string `mapstructure:"label"` + Name string `yaml:"name"` + Label string `yaml:"label"` } type Package struct { @@ -33,14 +33,14 @@ type Package struct { URL string `yaml:"url"` Dir string `yaml:"dir"` Kind string `yaml:"kind"` - ProviderOpt ProviderOptSpec `mapstructure:"provider"` - ProviderKind ProviderKind `mapstructure:"providerKind"` - Registry bool `mapstructure:"registry"` + ProviderOpt ProviderOptSpec `yaml:"provider"` + ProviderKind ProviderKind `yaml:"providerKind"` + Registry bool `yaml:"registry"` } type RegistrySpec struct { - BaseURI string `mapstructure:"url"` - Label string `mapstructure:"label"` + BaseURI string `yaml:"url"` + Label string `yaml:"label"` } type ProviderPattern map[string]ProviderKind @@ -51,7 +51,7 @@ type FuryFile struct { Roles []Package `yaml:"roles"` Modules []Package `yaml:"modules"` Bases []Package `yaml:"bases"` - Provider ProviderPattern `mapstructure:"provider"` + Provider ProviderPattern `yaml:"provider"` } func NewFuryFile(path string) (*FuryFile, error) { From 05489d2ce1ba4a1256785e74bf43f59a35adfd29 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 14 Mar 2023 19:25:00 +0100 Subject: [PATCH 165/383] fix(distribution): refine the excluded folders from template funcs invokations --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index a72a8ee21..a5686595e 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -154,7 +154,7 @@ func (d *Distribution) Exec() error { return err } - tfCfg, err := template.NewConfig(furyctlMerger, preTfMerger, []string{"source/manifests", ".gitignore"}) + tfCfg, err := template.NewConfig(furyctlMerger, preTfMerger, []string{"manifests", ".gitignore"}) if err != nil { return fmt.Errorf("error creating template config: %w", err) } @@ -185,7 +185,7 @@ func (d *Distribution) Exec() error { return err } - mCfg, err := template.NewConfig(furyctlMerger, postTfMerger, []string{"source/terraform", ".gitignore"}) + mCfg, err := template.NewConfig(furyctlMerger, postTfMerger, []string{"terraform", ".gitignore"}) if err != nil { return fmt.Errorf("error creating template config: %w", err) } @@ -214,7 +214,7 @@ func (d *Distribution) Exec() error { return err } - mCfg, err := template.NewConfig(furyctlMerger, postTfMerger, []string{"source/terraform", ".gitignore"}) + mCfg, err := template.NewConfig(furyctlMerger, postTfMerger, []string{"terraform", ".gitignore"}) if err != nil { return fmt.Errorf("error creating template config: %w", err) } From 851c8059c8ca87f480cfa42d987b194bcc1505d8 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 14 Mar 2023 19:25:12 +0100 Subject: [PATCH 166/383] feat(distribution): make output path absolute and print it in debug mode --- internal/distribution/iac.go | 12 +++++++++++- internal/template/funcmap.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/distribution/iac.go b/internal/distribution/iac.go index 6ce7b0641..0e58d00dd 100644 --- a/internal/distribution/iac.go +++ b/internal/distribution/iac.go @@ -7,6 +7,7 @@ package distribution import ( "errors" "fmt" + "log" "os" "path/filepath" @@ -40,10 +41,17 @@ func NewIACBuilder( noOverwrite, dryRun bool, ) *IACBuilder { + absOutDir, err := filepath.Abs(outDir) + if err != nil { + log.Fatalf("error getting absolute path for %s: %v", outDir, err) + + return nil + } + return &IACBuilder{ furyctlFile: furyctlFile, distroPath: distroPath, - outDir: outDir, + outDir: absOutDir, noOverwrite: noOverwrite, dryRun: dryRun, } @@ -109,6 +117,8 @@ func (m *IACBuilder) Build() error { } } + logrus.Debugf("output directory = %s", m.outDir) + templateModel, err := template.NewTemplateModel( sourcePath, m.outDir, diff --git a/internal/template/funcmap.go b/internal/template/funcmap.go index 51849899a..4855629bd 100644 --- a/internal/template/funcmap.go +++ b/internal/template/funcmap.go @@ -20,7 +20,7 @@ func NewFuncMap() FuncMap { return FuncMap{FuncMap: sprig.TxtFuncMap()} } -func (f *FuncMap) Add(name string, fn interface{}) { +func (f *FuncMap) Add(name string, fn any) { f.FuncMap[name] = fn } From fa37eafc2a435c4323149eca4bc125fc40b8a13a Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 14 Mar 2023 19:25:20 +0100 Subject: [PATCH 167/383] chore(tests): update distro fake data --- .../common/data/furyctl-defaults.yaml | 17 +- test/data/expensive/common/data/kfd.yaml | 2 +- .../public/ekscluster-kfd-v1alpha2.json | 368 ++++++++++++------ .../data/templates/distribution/_helpers.tpl | 135 ++++++- 4 files changed, 374 insertions(+), 148 deletions(-) diff --git a/test/data/expensive/common/data/furyctl-defaults.yaml b/test/data/expensive/common/data/furyctl-defaults.yaml index 276460eac..c3513545c 100644 --- a/test/data/expensive/common/data/furyctl-defaults.yaml +++ b/test/data/expensive/common/data/furyctl-defaults.yaml @@ -129,6 +129,11 @@ data: alertmanager: deadManSwitchWebhookUrl: "" slackWebhookUrl: "" + # networking module configuration + networking: + overrides: + nodeSelector: null + tolerations: null # policy module configuration policy: overrides: @@ -189,17 +194,7 @@ data: dex: # see dex documentation for more information connectors: [] - aws: - overrides: - nodeSelector: null - tolerations: null - ebsCsiDriver: - iamRoleArn: arn:aws:iam::123456789012:role/ebs-csi-controller-role - loadBalancerController: - iamRoleArn: arn:aws:iam::123456789012:role/example-load-balancer-controller - clusterAutoscaler: - region: eu-west-1 - iamRoleArn: arn:aws:iam::123456789012:role/example-cluster-autoscaler + templates: includes: - ".*\\.yaml" diff --git a/test/data/expensive/common/data/kfd.yaml b/test/data/expensive/common/data/kfd.yaml index b15cdf551..cfdc5dd19 100644 --- a/test/data/expensive/common/data/kfd.yaml +++ b/test/data/expensive/common/data/kfd.yaml @@ -5,7 +5,7 @@ version: v1.24.1 modules: auth: v0.0.2 - aws: v2.0.0 + aws: v2.1.0 dr: v1.10.1 ingress: v1.13.1 logging: v3.0.1 diff --git a/test/data/expensive/common/data/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/expensive/common/data/schemas/public/ekscluster-kfd-v1alpha2.json index 37842f285..9f6f91dc9 100644 --- a/test/data/expensive/common/data/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/expensive/common/data/schemas/public/ekscluster-kfd-v1alpha2.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2-public.json", "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", "type": "object", "properties": { @@ -93,21 +93,6 @@ } } } - }, - { - "properties": { - "infrastructure": { - "properties": { - "vpc": { - "properties": { - "vpn": { - "type": "null" - } - } - } - } - } - } } ] }, @@ -179,11 +164,10 @@ "additionalProperties": false, "properties": { "bucketName": { - "type": "string" + "$ref": "#/$defs/Types.AwsS3BucketName" }, "keyPrefix": { - "type": "string", - "maxLength": 37 + "$ref": "#/$defs/Types.AwsS3KeyPrefix" }, "region": { "$ref": "#/$defs/Types.AwsRegion" @@ -313,7 +297,8 @@ "type": "array", "items": { "type": "string" - } + }, + "minItems": 1 }, "allowedFromCidrs": { "type": "array", @@ -348,13 +333,24 @@ "nodeAllowedSshPublicKey": { "anyOf": [ { - "$ref": "#/$defs/Types.SshPubKey" + "$ref": "#/$defs/Types.AwsSshPubKey" }, { "$ref": "#/$defs/Types.FileRef" } ] }, + "nodePoolsLaunchKind": { + "type": "string", + "enum": [ + "launch_configurations", + "launch_templates", + "both" + ] + }, + "logRetentionDays": { + "type": "integer" + }, "nodePools": { "type": "array", "items": { @@ -367,7 +363,8 @@ }, "required": [ "nodeAllowedSshPublicKey", - "nodePools" + "nodePools", + "nodePoolsLaunchKind" ] }, "Spec.Kubernetes.APIServerEndpointAccess": { @@ -440,7 +437,6 @@ } }, "required": [ - "ami", "instance", "name", "size" @@ -516,7 +512,8 @@ "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" - } + }, + "minItems": 1 }, "protocol": { "$ref": "#/$defs/Types.AwsIpProtocol" @@ -748,6 +745,9 @@ "monitoring": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" }, + "networking": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking" + }, "policy": { "$ref": "#/$defs/Spec.Distribution.Modules.Policy" } @@ -763,7 +763,7 @@ "additionalProperties": false, "properties": { "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides" }, "baseDomain": { "type": "string" @@ -777,8 +777,8 @@ "dns": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" }, - "externalDns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + "forecastle": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Forecastle" } }, "required": [ @@ -787,6 +787,24 @@ "nginx" ] }, + "Spec.Distribution.Modules.Ingress.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "ingresses": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides.Ingresses" + }, + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { "type": "object", "additionalProperties": false, @@ -796,6 +814,15 @@ } } }, + "Spec.Distribution.Modules.Ingress.Forecastle": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Ingress.Nginx": { "type": "object", "additionalProperties": false, @@ -806,6 +833,9 @@ }, "tls": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -864,6 +894,9 @@ "properties": { "clusterIssuer": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -884,9 +917,6 @@ "type": { "type": "string", "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" } }, "required": [ @@ -895,42 +925,6 @@ "email" ] }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.ExternalDNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "privateIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "publicIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "privateIamRoleArn", - "publicIamRoleArn" - ] - }, "Spec.Distribution.Modules.Ingress.DNS": { "type": "object", "additionalProperties": false, @@ -940,6 +934,9 @@ }, "private": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -972,15 +969,11 @@ }, "create": { "type": "boolean" - }, - "vpcId": { - "type": "string" } }, "required": [ "name", - "create", - "vpcId" + "create" ] }, "Spec.Distribution.Modules.Logging": { @@ -992,6 +985,15 @@ }, "opensearch": { "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + }, + "cerebro": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Cerebro" + }, + "minio": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Minio" + }, + "operator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Operator" } }, "required": ["opensearch"] @@ -1009,12 +1011,42 @@ }, "storageSize": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ "type" ] }, + "Spec.Distribution.Modules.Logging.Cerebro": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Minio": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Operator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Monitoring": { "type": "object", "additionalProperties": false, @@ -1027,6 +1059,18 @@ }, "alertmanager": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + }, + "grafana": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Grafana" + }, + "blackboxExporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.BlackboxExporter" + }, + "kubeStateMetrics": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.KubeStateMetrics" + }, + "x509Exporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.X509Exporter" } } }, @@ -1060,6 +1104,63 @@ } } }, + "Spec.Distribution.Modules.Monitoring.Grafana": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.BlackboxExporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.KubeStateMetrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.X509Exporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Networking": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + }, + "tigeraOperator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking.TigeraOperator" + } + } + }, + "Spec.Distribution.Modules.Networking.TigeraOperator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Policy": { "type": "object", "additionalProperties": false, @@ -1081,6 +1182,9 @@ "items": { "type": "string" } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } } }, @@ -1103,6 +1207,9 @@ "properties": { "eks": { "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["eks"] @@ -1115,16 +1222,13 @@ "$ref": "#/$defs/Types.AwsRegion" }, "bucketName": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "$ref": "#/$defs/Types.AwsS3BucketName", + "maxLength": 49 } }, "required": [ "region", - "bucketName", - "iamRoleArn" + "bucketName" ] }, "Spec.Distribution.Modules.Auth": { @@ -1137,6 +1241,9 @@ "provider": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" }, + "baseDomain": { + "type": "string" + }, "pomerium": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" }, @@ -1159,11 +1266,7 @@ } }, "then": { - "properties": { - "auth": { - "required": ["dex", "pomerium"] - } - } + "required": ["dex", "pomerium", "baseDomain"] }, "else": { "properties": { @@ -1285,6 +1388,9 @@ }, "policy": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -1318,6 +1424,9 @@ "properties": { "connectors": { "type": "array" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["connectors"] @@ -1326,54 +1435,46 @@ "type": "object", "additionalProperties": false, "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } }, "ebsCsiDriver": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } }, "loadBalancerController": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] - } - } - }, - - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + } }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" + "ebsSnapshotController": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" } - }, - "required": [ - "iamRoleArn", - "region" - ] + } }, "Types.SemVer": { @@ -1445,6 +1546,10 @@ "type": "string", "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" }, + "Types.AwsSshPubKey": { + "type": "string", + "pattern": "^ssh\\-(ed25519|rsa)\\s+" + }, "Types.AwsSubnetId": { "type": "string", "pattern": "^subnet\\-[0-9a-f]{17}$" @@ -1458,6 +1563,24 @@ "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" }, + "Types.AwsS3BucketName": { + "type": "string", + "allOf": [ + { + "pattern": "^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$" + }, + { + "not": { + "pattern": "^xn--|-s3alias$" + } + } + ] + }, + "Types.AwsS3KeyPrefix": { + "type": "string", + "pattern": "^[A-z0-9][A-z0-9!-_.*'()]+$", + "maxLength": 960 + }, "Types.KubeLabels": { "type": "object", "additionalProperties": { "type": "string" } @@ -1545,6 +1668,21 @@ } } }, + "Types.FuryModuleComponentOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Types.FuryModuleOverridesIngress": { "type": "object", "additionalProperties": false, diff --git a/test/data/expensive/common/data/templates/distribution/_helpers.tpl b/test/data/expensive/common/data/templates/distribution/_helpers.tpl index f1e63782f..e103c47b0 100644 --- a/test/data/expensive/common/data/templates/distribution/_helpers.tpl +++ b/test/data/expensive/common/data/templates/distribution/_helpers.tpl @@ -1,13 +1,60 @@ -{{ define "commonNodeSelector" }} - {{- $indent := .indent | default 8 -}} - {{ .spec.distribution.common.nodeSelector | toYaml | indent $indent | trim }} +{{- define "nodeSelector" -}} + {{- $indent := default 8 (index . "indent") -}} + + {{- $module := index .spec.distribution.modules .module -}} + + {{- $package := dict -}} + {{- if $module -}} + {{- $package = index $module .package -}} + {{- end -}} + + {{- $packageNodeSelector := dict -}} + {{- if and ($package) (index $package "overrides") -}} + {{- $packageNodeSelector = index $package.overrides "nodeSelector" -}} + {{- end -}} + + {{- $moduleNodeSelector := dict -}} + {{- if and ($module) (index $module "overrides") -}} + {{- $moduleNodeSelector = index $module.overrides "nodeSelector" -}} + {{- end -}} + + {{- $nodeSelector := coalesce + $packageNodeSelector + $moduleNodeSelector + (index .spec.distribution.common "nodeSelector") -}} + + {{- $nodeSelector | toYaml | indent $indent | trim -}} {{- end -}} -{{ define "commonTolerations" }} - {{- $indent := .indent | default 8 -}} - {{ .spec.distribution.common.tolerations | toYaml | indent $indent | trim }} +{{- define "tolerations" -}} + {{- $indent := default 8 (index . "indent") -}} + + {{- $module := index .spec.distribution.modules .module -}} + + {{- $package := dict -}} + {{- if $module -}} + {{- $package = index $module .package -}} + {{- end -}} + + {{- $packageTolerations := dict -}} + {{- if and ($package) (index $package "overrides") -}} + {{- $packageTolerations = index $package.overrides "tolerations" -}} + {{- end -}} + + {{- $moduleTolerations := dict -}} + {{- if and ($module) (index $module "overrides") -}} + {{- $moduleTolerations = index $module.overrides "tolerations" -}} + {{- end -}} + + {{- $tolerations := coalesce + $packageTolerations + $moduleTolerations + (index .spec.distribution.common "tolerations") -}} + + {{- $tolerations | toYaml | indent $indent | trim -}} {{- end -}} + {{ define "globalIngressClass" }} {{- if eq .spec.distribution.modules.ingress.nginx.type "single" -}} "nginx" @@ -40,6 +87,18 @@ {{- end -}} {{ end }} +{{/* ingressHostAuth { module: , package: , prefix: , spec: "." } */}} +{{ define "ingressHostAuth" }} + {{- $module := index .spec.distribution.modules .module -}} + {{- $package := index $module.overrides.ingresses .package -}} + {{- $host := $package.host -}} + {{- if $host -}} + {{ $host }} + {{- else -}} + {{ print .prefix .spec.distribution.modules.auth.baseDomain }} + {{- end -}} +{{ end }} + {{/* ingressTls { module: , package: , prefix: , spec: "." } */}} {{- define "ingressTls" -}} {{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "none" -}} @@ -53,23 +112,25 @@ {{- end }} {{- end -}} -{{ define "pomeriumHost" }} - {{- template "ingressHost" (dict "module" "auth" "package" "pomerium" "prefix" "pomerium.internal." "spec" .spec) -}} -{{ end }} - -{{ define "ingressAuthUrl" -}} -"https://{{ template "pomeriumHost" . }}/verify?uri=$scheme://$host$request_uri" +{{/* ingressTlsAuth { module: , package: , prefix: , spec: "." } */}} +{{- define "ingressTlsAuth" -}} +{{ if eq .spec.distribution.modules.ingress.nginx.tls.provider "none" -}} + {{ else }} + tls: + - hosts: + - {{ template "ingressHostAuth" . }} + {{- if eq .spec.distribution.modules.ingress.nginx.tls.provider "certManager" }} + secretName: {{ lower .package }}-tls + {{- end }} {{- end }} +{{- end -}} -{{ define "ingressAuthSignin" -}} -"https://{{ template "pomeriumHost" . }}/?uri=$scheme://$host$request_uri" -{{- end }} +{{ define "pomeriumHost" }} + {{- template "ingressHost" (dict "module" "auth" "package" "pomerium" "prefix" "pomerium." "spec" .spec) -}} +{{ end }} {{ define "ingressAuth" }} -{{- if eq .spec.distribution.modules.auth.provider.type "sso" -}} - nginx.ingress.kubernetes.io/auth-url: {{ template "ingressAuthUrl" . }} - nginx.ingress.kubernetes.io/auth-signin: {{ template "ingressAuthSignin" . }} -{{- else if eq .spec.distribution.modules.auth.provider.type "basicAuth" -}} +{{- if eq .spec.distribution.modules.auth.provider.type "basicAuth" -}} nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required' @@ -83,9 +144,41 @@ cert-manager.io/cluster-issuer: {{ .spec.distribution.modules.ingress.certManage {{ end }} {{ define "alertmanagerUrl" }} - {{- template "ingressHost" (dict "module" "monitoring" "package" "alertmanager" "prefix" "alertmanager.internal." "spec" .) -}} + {{- template "ingressHost" (dict "module" "monitoring" "package" "alertmanager" "prefix" "alertmanager." "spec" .) -}} {{ end }} {{ define "prometheusUrl" }} - {{- template "ingressHost" (dict "module" "monitoring" "package" "prometheus" "prefix" "prometheus.internal." "spec" .) -}} + {{- template "ingressHost" (dict "module" "monitoring" "package" "prometheus" "prefix" "prometheus." "spec" .) -}} +{{ end }} + +{{ define "grafanaUrl" }} + {{- template "ingressHost" (dict "module" "monitoring" "package" "grafana" "prefix" "grafana." "spec" .) -}} +{{ end }} + +{{ define "minioUrl" }} + {{- template "ingressHost" (dict "module" "logging" "package" "minio" "prefix" "minio." "spec" .) -}} +{{ end }} + +{{ define "opensearchDashboardsUrl" }} + {{- template "ingressHost" (dict "module" "logging" "package" "opensearchDashboards" "prefix" "opensearch-dashboards." "spec" .) -}} +{{ end }} + +{{ define "forecastleUrl" }} + {{- template "ingressHost" (dict "module" "ingress" "package" "forecastle" "prefix" "directory." "spec" .) -}} +{{ end }} + +{{ define "cerebroUrl" }} + {{- template "ingressHost" (dict "module" "logging" "package" "cerebro" "prefix" "cerebro." "spec" .) -}} +{{ end }} + +{{ define "gpmUrl" }} + {{- template "ingressHost" (dict "module" "policy" "package" "gpm" "prefix" "gpm." "spec" .) -}} +{{ end }} + +{{ define "pomeriumUrl" }} + {{- template "ingressHostAuth" (dict "module" "auth" "package" "pomerium" "prefix" "pomerium." "spec" .) -}} +{{ end }} + +{{ define "dexUrl" }} + {{- template "ingressHostAuth" (dict "module" "auth" "package" "dex" "prefix" "login." "spec" .) -}} {{ end }} From ca06912feaf769fce6e3a68e4a5d9a7a4945d2d2 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 14 Mar 2023 19:29:56 +0100 Subject: [PATCH 168/383] chore(distribution): properly handle errors in IACBuilder constructor. --- cmd/dump/template.go | 8 +++++++- internal/distribution/iac.go | 9 +++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index b97899b78..72e4bc167 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -86,13 +86,19 @@ The generated folder will be created starting from a provided templates folder a logrus.Info("Generating distribution manifests...") - distroManBuilder := distribution.NewIACBuilder( + distroManBuilder, err := distribution.NewIACBuilder( furyctlFile, res.RepoPath, flags.OutDir, flags.NoOverwrite, flags.DryRun, ) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while creating distribution manifest builder: %w", err) + } if err := distroManBuilder.Build(); err != nil { cmdEvent.AddErrorMessage(err) diff --git a/internal/distribution/iac.go b/internal/distribution/iac.go index 0e58d00dd..2cd9803a6 100644 --- a/internal/distribution/iac.go +++ b/internal/distribution/iac.go @@ -7,7 +7,6 @@ package distribution import ( "errors" "fmt" - "log" "os" "path/filepath" @@ -40,12 +39,10 @@ func NewIACBuilder( outDir string, noOverwrite, dryRun bool, -) *IACBuilder { +) (*IACBuilder, error) { absOutDir, err := filepath.Abs(outDir) if err != nil { - log.Fatalf("error getting absolute path for %s: %v", outDir, err) - - return nil + return nil, fmt.Errorf("error getting absolute path for %s: %w", outDir, err) } return &IACBuilder{ @@ -54,7 +51,7 @@ func NewIACBuilder( outDir: absOutDir, noOverwrite: noOverwrite, dryRun: dryRun, - } + }, nil } func (m *IACBuilder) Build() error { From 86ae0d584e7bbcb0c07ac5d479894d20cbac5bd2 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 27 Mar 2023 17:10:44 +0200 Subject: [PATCH 169/383] chore: tweak a few error messages --- internal/apis/kfd/v1alpha2/eks/creator.go | 4 ++-- internal/tool/terraform/runner.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 971749f25..7f4ecbbd6 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -397,10 +397,10 @@ func (v *ClusterCreator) storeClusterConfig() error { Kubeconfig: v.paths.Kubeconfig, }, true, true, false) - logrus.Info("Storing cluster config...") + logrus.Info("Applying secret...") if err := runner.Apply(secretPath); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) + return fmt.Errorf("error while applying secret: %w", err) } return nil diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index 1df059998..80f08e892 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -82,7 +82,7 @@ func (r *Runner) Plan(timestamp int64, params ...string) error { WorkDir: r.paths.WorkDir, }) if err := cmd.Run(); err != nil { - return fmt.Errorf("error running terraform plan: %w", err) + return fmt.Errorf("command execution failed: %w", err) } err := os.WriteFile(path.Join(r.paths.Plan, From fdfefead04cb9c6d1c07e5dbc644f8bc90327c1e Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 31 Mar 2023 14:56:47 +0200 Subject: [PATCH 170/383] chore: update data structures after distro updates --- go.mod | 4 +++- go.sum | 3 --- .../apis/kfd/v1alpha2/eks/create/distribution.go | 12 ++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 7629c868c..119028307 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/sighupio/furyctl go 1.19 +replace github.com/sighupio/fury-distribution => ../../distribution + require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/briandowns/spinner v1.19.0 @@ -27,7 +29,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061 + github.com/sighupio/fury-distribution v1.25.0 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 148d15bb9..373ab29e3 100644 --- a/go.sum +++ b/go.sum @@ -76,7 +76,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -301,8 +300,6 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061 h1:kMlliGNS4qbnPcW7CaecQgS1Ye8kA/eXdslI4ilrzjU= -github.com/sighupio/fury-distribution v1.24.1-0.20230221155837-99cbb28cf061/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index a5686595e..78962c535 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -359,24 +359,24 @@ func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, e Data: private.SpecDistribution{ Modules: private.SpecDistributionModules{ Aws: &private.SpecDistributionModulesAws{ - EbsCsiDriver: &private.SpecDistributionModulesAwsEbsCsiDriver{ + EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), }, - LoadBalancerController: &private.SpecDistributionModulesAwsLoadBalancerController{ + LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), }, - ClusterAutoscaler: &private.SpecDistributionModulesAwsClusterAutoScaler{ + ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), }, }, Ingress: private.SpecDistributionModulesIngress{ - ExternalDns: &private.SpecDistributionModulesIngressExternalDNS{ + ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), }, - CertManager: &private.SpecDistributionModulesIngressCertManager{ + CertManager: private.SpecDistributionModulesIngressCertManager{ ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ - Route53: &private.SpecDistributionModulesIngressClusterIssuerRoute53{ + Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), }, }, From e24cb194a75213292511466ebdef89c755518743 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 31 Mar 2023 17:58:52 +0200 Subject: [PATCH 171/383] chore: pin fury-distribution version to the latest 'feature/furyctl-next' commit --- go.mod | 4 +--- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 119028307..ef3700ef0 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/sighupio/furyctl go 1.19 -replace github.com/sighupio/fury-distribution => ../../distribution - require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/briandowns/spinner v1.19.0 @@ -29,7 +27,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.0 + github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 373ab29e3..0566aa02c 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,10 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sighupio/fury-distribution v1.25.0 h1:CfQ7nEXNAmFuquHYCDMPYcAUCMrqxfhW6/MJCyVCeGY= +github.com/sighupio/fury-distribution v1.25.0/go.mod h1:EOx1HTa5Mz/KYItWwYp++EPxwqV90nBZ68nXSdaKaWE= +github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= +github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 4f464492f37f4c475818299fb04ce8592383657d Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 4 Apr 2023 09:58:16 +0200 Subject: [PATCH 172/383] chore(tests): replace panics with t.Fatal --- internal/template/generator_test.go | 12 ++++++------ internal/template/model_test.go | 2 +- test/e2e/furyctl_test.go | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/template/generator_test.go b/internal/template/generator_test.go index 667782ab5..589342ae0 100644 --- a/internal/template/generator_test.go +++ b/internal/template/generator_test.go @@ -34,7 +34,7 @@ func TestTemplateModel_Will_Generate_UserHello(t *testing.T) { confYaml, err := yaml.Marshal(conf) if err != nil { - panic(err) + t.Fatal(err) } path, err := os.MkdirTemp("", "test") @@ -61,7 +61,7 @@ func TestTemplateModel_Will_Generate_UserHello(t *testing.T) { result, err := os.ReadFile(path + "/target/test.md") if err != nil { - panic(err) + t.Fatal(err) } expectedRes := "A nice day at tes" @@ -82,7 +82,7 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_Env(t *testing.T) { confYaml, err := yaml.Marshal(conf) if err != nil { - panic(err) + t.Fatal(err) } path, err := os.MkdirTemp("", "test") @@ -113,7 +113,7 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_Env(t *testing.T) { result, err := os.ReadFile(path + "/target/test.md") if err != nil { - panic(err) + t.Fatal(err) } expectedRes := "A nice day at Tym" @@ -136,7 +136,7 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_File(t *testing.T) { confYaml, err := yaml.Marshal(conf) if err != nil { - panic(err) + t.Fatal(err) } err = os.Mkdir(path+"/source", os.ModePerm) @@ -165,7 +165,7 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_File(t *testing.T) { result, err := os.ReadFile(path + "/target/test.md") if err != nil { - panic(err) + t.Fatal(err) } expectedRes := "A nice day at Tymlate! It's a nice day!" diff --git a/internal/template/model_test.go b/internal/template/model_test.go index 68f38971f..9a13c928a 100644 --- a/internal/template/model_test.go +++ b/internal/template/model_test.go @@ -29,7 +29,7 @@ func TestNewTemplateModel(t *testing.T) { confYaml, err := yaml.Marshal(conf) if err != nil { - panic(err) + t.Fatal(err) } path, err := os.MkdirTemp("", "test") diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 7258fffbf..571213017 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -84,6 +84,7 @@ var ( Fail(err.Error()) } } + BackupEnvVars = func(vars ...string) func() { backup := make(map[string]string) remove := make([]string, 0) From 24770847316a6b0f2a09b9f450e8ffad73132d3a Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 28 Mar 2023 16:22:09 +0200 Subject: [PATCH 173/383] feat: added prompt when found existing vpn --- internal/apis/kfd/v1alpha2/eks/vpn.go | 39 ++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 35bd5a1c3..4769be5b2 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -28,7 +28,6 @@ import ( var ( ErrAutoConnectWithoutVpn = errors.New("autoconnect is not supported without a VPN configuration") ErrReadStdin = errors.New("error reading from stdin") - ErrOpenvpnRunning = errors.New("an openvpn process is already running, please kill it and try again") ) type VpnConnector struct { @@ -75,10 +74,17 @@ func (v *VpnConnector) Connect() error { return ErrAutoConnectWithoutVpn } - if err := v.checkExistingOpenVPN(); err != nil { + olVPN, pid, err := v.checkExistingOpenVPN() + if err != nil { return err } + if olVPN { + if err := v.promptAutoConnect(pid); err != nil { + return err + } + } + return v.startOpenVPN() } @@ -178,21 +184,29 @@ func (v *VpnConnector) copyOpenvpnToWorkDir(clientName string) error { return nil } -func (*VpnConnector) checkExistingOpenVPN() error { +func (*VpnConnector) checkExistingOpenVPN() (bool, int32, error) { + pid := int32(0) + + found := false + processes, err := process.Processes() if err != nil { - return fmt.Errorf("error getting processes: %w", err) + return false, pid, fmt.Errorf("error getting processes: %w", err) } for _, p := range processes { name, _ := p.Name() //nolint:errcheck // we don't care about the error here + pid = p.Pid + if name == "openvpn" { - return ErrOpenvpnRunning + found = true + + break } } - return nil + return found, pid, nil } func (v *VpnConnector) startOpenVPN() error { @@ -221,6 +235,19 @@ func (v *VpnConnector) startOpenVPN() error { return nil } +func (*VpnConnector) promptAutoConnect(pid int32) error { + logrus.Warnf("There is already a VPN connection process running with PID %d,"+ + " please check it before you continue.\n", pid) + + logrus.Info("Press enter to continue or CTRL-C to abort...") + + if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil { + return fmt.Errorf("%w: %v", ErrReadStdin, err) + } + + return nil +} + func (v *VpnConnector) prompt() error { connectMsg := "Please connect to the VPN before continuing" From 2965da083d08d4f4ecd8b4bc817808fe2c3ac234 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:36:11 +0200 Subject: [PATCH 174/383] chore: apply suggestions from code review Co-authored-by: Claudio Beatrice --- internal/apis/kfd/v1alpha2/eks/vpn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 4769be5b2..1fd56a42d 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -191,7 +191,7 @@ func (*VpnConnector) checkExistingOpenVPN() (bool, int32, error) { processes, err := process.Processes() if err != nil { - return false, pid, fmt.Errorf("error getting processes: %w", err) + return found, pid, fmt.Errorf("error getting processes: %w", err) } for _, p := range processes { @@ -237,7 +237,7 @@ func (v *VpnConnector) startOpenVPN() error { func (*VpnConnector) promptAutoConnect(pid int32) error { logrus.Warnf("There is already a VPN connection process running with PID %d,"+ - " please check it before you continue.\n", pid) + " please confirm it is intended to be up before you continue.\n", pid) logrus.Info("Press enter to continue or CTRL-C to abort...") From 39d82934760c6472de92ce250bbc667f5edd33b1 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 4 Apr 2023 18:04:06 +0200 Subject: [PATCH 175/383] chore: rename vpn var --- internal/apis/kfd/v1alpha2/eks/vpn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 1fd56a42d..717a2230e 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -74,12 +74,12 @@ func (v *VpnConnector) Connect() error { return ErrAutoConnectWithoutVpn } - olVPN, pid, err := v.checkExistingOpenVPN() + vpn, pid, err := v.checkExistingOpenVPN() if err != nil { return err } - if olVPN { + if vpn { if err := v.promptAutoConnect(pid); err != nil { return err } From 6458ea81f6bdcc8d33cb696ffeee4d8db1b8b6d6 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 4 Apr 2023 18:07:46 +0200 Subject: [PATCH 176/383] chore: refactor checkExistingOpenVPN() --- internal/apis/kfd/v1alpha2/eks/vpn.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 717a2230e..379f8f283 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -185,28 +185,25 @@ func (v *VpnConnector) copyOpenvpnToWorkDir(clientName string) error { } func (*VpnConnector) checkExistingOpenVPN() (bool, int32, error) { - pid := int32(0) - - found := false - processes, err := process.Processes() if err != nil { - return found, pid, fmt.Errorf("error getting processes: %w", err) + return false, 0, fmt.Errorf("error getting processes: %w", err) } for _, p := range processes { - name, _ := p.Name() //nolint:errcheck // we don't care about the error here + name, err := p.Name() + if err != nil { + logrus.Warning(err) - pid = p.Pid + continue + } if name == "openvpn" { - found = true - - break + return true, p.Pid, nil } } - return found, pid, nil + return false, 0, nil } func (v *VpnConnector) startOpenVPN() error { From 1b956a3024debfba291bd04a6d226839d3ed79cb Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 3 Apr 2023 18:18:49 +0200 Subject: [PATCH 177/383] fix: converted file and env from object to string in template engine --- internal/template/generator_test.go | 7 +-- internal/template/mapper/mapper.go | 49 +++++++++---------- internal/template/mapper/mapper_test.go | 8 +-- .../template/complex-dry-run/furyctl.yaml | 20 ++++---- .../e2e/dump/template/complex/furyctl.yaml | 20 ++++---- .../e2e/validate/config/nodistro/furyctl.yaml | 4 +- .../e2e/validate/config/wrong/furyctl.yaml | 4 +- .../dependencies/correct/furyctl.yaml | 4 +- .../dependencies/missing/furyctl.yaml | 4 +- .../validate/dependencies/wrong/furyctl.yaml | 4 +- 10 files changed, 60 insertions(+), 64 deletions(-) diff --git a/internal/template/generator_test.go b/internal/template/generator_test.go index 589342ae0..93a54fb54 100644 --- a/internal/template/generator_test.go +++ b/internal/template/generator_test.go @@ -7,6 +7,7 @@ package template_test import ( + "fmt" "os" "testing" gotemplate "text/template" @@ -18,7 +19,7 @@ import ( ) type Meta struct { - Name map[string]any `yaml:"name,flow"` + Name string `yaml:"name,flow"` } func TestTemplateModel_Will_Generate_UserHello(t *testing.T) { @@ -73,7 +74,7 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_Env(t *testing.T) { conf := map[string]any{ "data": map[string]any{ "meta": Meta{ - Name: map[string]any{"env://TEST_USER_TYMLATE": ""}, + Name: "{env://TEST_USER_TYMLATE}", }, }, } @@ -127,7 +128,7 @@ func TestTemplateModel_Will_Generate_Dynamic_Values_From_File(t *testing.T) { conf := map[string]any{ "data": map[string]any{ "meta": Meta{ - Name: map[string]any{"file://" + path + "/tymlate_test_file.txt": ""}, + Name: fmt.Sprintf("{file://%s/tymlate_test_file.txt}", path), }, }, } diff --git a/internal/template/mapper/mapper.go b/internal/template/mapper/mapper.go index 86edd1fa7..d7e611d77 100644 --- a/internal/template/mapper/mapper.go +++ b/internal/template/mapper/mapper.go @@ -16,10 +16,7 @@ const ( File = "file" ) -var ( - errKeyIsNotAString = errors.New("key is not a string") - errUnknownKey = errors.New("unknown key") -) +var errUnknownKey = errors.New("unknown key") type Mapper struct { context map[string]map[any]any @@ -33,7 +30,7 @@ func (m *Mapper) MapDynamicValues() (map[string]map[any]any, error) { mappedCtx := make(map[string]map[any]any, len(m.context)) for k, c := range m.context { - res, err := injectDynamicRes(c, c, k) + res, err := injectDynamicRes(c) mappedCtx[k] = res if err != nil { @@ -57,31 +54,11 @@ func (*Mapper) MapEnvironmentVars() map[any]any { func injectDynamicRes( m map[any]any, - parent map[any]any, - parentKey string, ) (map[any]any, error) { for k, v := range m { - key, ok := k.(string) - if !ok { - return nil, fmt.Errorf("%v %w", k, errKeyIsNotAString) - } - - spl := strings.Split(key, "://") - - if len(spl) > 1 { - val, err := ParseDynamicValue(k) - if err != nil { - return nil, err - } - - parent[parentKey] = val - - continue - } - vMap, checkMap := v.(map[any]any) if checkMap { - if _, err := injectDynamicRes(vMap, m, k.(string)); err != nil { + if _, err := injectDynamicRes(vMap); err != nil { return nil, err } @@ -92,7 +69,7 @@ func injectDynamicRes( if checkArr { for _, j := range vArr { if j, ok := j.(map[any]any); ok { - if _, err := injectDynamicRes(j, m, k.(string)); err != nil { + if _, err := injectDynamicRes(j); err != nil { return nil, err } } @@ -100,6 +77,24 @@ func injectDynamicRes( continue } + + val, ok := v.(string) + if !ok { + continue + } + + spl := strings.Split(val, "://") + + if len(spl) > 1 { + val, err := ParseDynamicValue(val) + if err != nil { + return nil, err + } + + m[k] = val + + continue + } } return m, nil diff --git a/internal/template/mapper/mapper_test.go b/internal/template/mapper/mapper_test.go index bddde9c2d..3c64e4a3f 100644 --- a/internal/template/mapper/mapper_test.go +++ b/internal/template/mapper/mapper_test.go @@ -74,8 +74,8 @@ func TestMapper_MapDynamicValues(t *testing.T) { dummyContext := map[string]map[any]any{ "data": { "meta": map[any]any{ - "name": map[any]any{"env://TEST_MAPPER_DYNAMIC_VALUE": ""}, - "value": map[any]any{fmt.Sprintf("file://%s/test_file.txt", path): ""}, + "name": "{env://TEST_MAPPER_DYNAMIC_VALUE}", + "value": fmt.Sprintf("{file://%s/test_file.txt}", path), }, }, } @@ -123,8 +123,8 @@ func TestMapper_MapDynamicValues_RelativePath(t *testing.T) { dummyContext := map[string]map[any]any{ "data": { "meta": map[any]any{ - "name": map[any]any{"env://TEST_MAPPER_DYNAMIC_VALUE": ""}, - "value": map[any]any{fmt.Sprintf("file://%s", filePath): ""}, + "name": "{env://TEST_MAPPER_DYNAMIC_VALUE}", + "value": fmt.Sprintf("{file://%s}", filePath), }, }, } diff --git a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml index 55672719d..f2fdc92f4 100644 --- a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml @@ -59,7 +59,7 @@ spec: # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal publicKeys: - "ssh-ed56778 XYX" -# - {file://relative/path/to/ssh.pub} +# - "{file://relative/path/to/ssh.pub}" # Github users to get ssh keys from githubUsersName: - lnovara @@ -169,9 +169,9 @@ spec: # can be certManager, secret or none provider: certManager # it uses the configuration below as default when certManager is chosen # secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly - # cert: {file://ssl.crt} - # key: {file://ssl.key} - # ca: {file://ssl.ca} + # cert: "{file://ssl.crt}" + # key: "{file://ssl.key}" + # ca: "{file://ssl.ca}" # the standard configuration for cert-manager on the ingress module certManager: # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity @@ -295,16 +295,16 @@ spec: type: none basicAuth: username: admin - password: {env://KFD_BASIC_AUTH_PASSWORD} + password: "{env://KFD_BASIC_AUTH_PASSWORD}" pomerium: secrets: # override environment variables here ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret - COOKIE_SECRET: {env://KFD_AUTH_POMERIUM_COOKIE_SECRET} + COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client - IDP_CLIENT_SECRET: {env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET} + IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret - SHARED_SECRET: {env://KFD_AUTH_POMERIUM_SHARED_SECRET} + SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" dex: # see dex documentation for more information connectors: @@ -315,8 +315,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug diff --git a/test/data/e2e/dump/template/complex/furyctl.yaml b/test/data/e2e/dump/template/complex/furyctl.yaml index 248910ced..2c3c8b9cc 100644 --- a/test/data/e2e/dump/template/complex/furyctl.yaml +++ b/test/data/e2e/dump/template/complex/furyctl.yaml @@ -59,7 +59,7 @@ spec: # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal publicKeys: - "ssh-ed56778 XYX" -# - {file://relative/path/to/ssh.pub} +# - "{file://relative/path/to/ssh.pub}" # Github users to get ssh keys from githubUsersName: - lnovara @@ -169,9 +169,9 @@ spec: # can be certManager, secret or none provider: certManager # it uses the configuration below as default when certManager is chosen # secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly - # cert: {file://ssl.crt} - # key: {file://ssl.key} - # ca: {file://ssl.ca} + # cert: "{file://ssl.crt}" + # key: "{file://ssl.key}" + # ca: "{file://ssl.ca}" # the standard configuration for cert-manager on the ingress module certManager: # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity @@ -290,16 +290,16 @@ spec: type: none basicAuth: username: admin - password: {env://KFD_BASIC_AUTH_PASSWORD} + password: "{env://KFD_BASIC_AUTH_PASSWORD}" pomerium: secrets: # override environment variables here ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret - COOKIE_SECRET: {env://KFD_AUTH_POMERIUM_COOKIE_SECRET} + COOKIE_SECRET: "{env://KFD_AUTH_POMERIUM_COOKIE_SECRET}" ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client - IDP_CLIENT_SECRET: {env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET} + IDP_CLIENT_SECRET: "{env://KFD_AUTH_POMERIUM_IDP_CLIENT_SECRET}" ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret - SHARED_SECRET: {env://KFD_AUTH_POMERIUM_SHARED_SECRET} + SHARED_SECRET: "{env://KFD_AUTH_POMERIUM_SHARED_SECRET}" dex: # see dex documentation for more information connectors: @@ -310,8 +310,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug diff --git a/test/data/e2e/validate/config/nodistro/furyctl.yaml b/test/data/e2e/validate/config/nodistro/furyctl.yaml index d655a48d1..e86abf55c 100644 --- a/test/data/e2e/validate/config/nodistro/furyctl.yaml +++ b/test/data/e2e/validate/config/nodistro/furyctl.yaml @@ -317,8 +317,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug diff --git a/test/data/e2e/validate/config/wrong/furyctl.yaml b/test/data/e2e/validate/config/wrong/furyctl.yaml index 1bba7fa33..9093211dc 100644 --- a/test/data/e2e/validate/config/wrong/furyctl.yaml +++ b/test/data/e2e/validate/config/wrong/furyctl.yaml @@ -318,8 +318,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index 6f4ffba1b..c4613bd55 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -317,8 +317,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug diff --git a/test/data/e2e/validate/dependencies/missing/furyctl.yaml b/test/data/e2e/validate/dependencies/missing/furyctl.yaml index d655a48d1..e86abf55c 100644 --- a/test/data/e2e/validate/dependencies/missing/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/missing/furyctl.yaml @@ -317,8 +317,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug diff --git a/test/data/e2e/validate/dependencies/wrong/furyctl.yaml b/test/data/e2e/validate/dependencies/wrong/furyctl.yaml index 7c8abc066..140417981 100644 --- a/test/data/e2e/validate/dependencies/wrong/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/wrong/furyctl.yaml @@ -319,8 +319,8 @@ spec: name: GitHub config: # Credentials can be string literals or pulled from the environment. - clientID: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID} - clientSecret: {env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET} + clientID: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_ID}" + clientSecret: "{env://KFD_AUTH_DEX_CONNECTORS_GITHUB_CLIENT_SECRET}" redirectURI: https://login.fury-demo.sighup.io/callback loadAllGroups: false teamNameField: slug From 31ed89bc701fe0f174cd3723707eaa93271a635d Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 3 Apr 2023 18:26:08 +0200 Subject: [PATCH 178/383] fix: error when parsing strings in format x://y --- internal/template/mapper/mapper.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/template/mapper/mapper.go b/internal/template/mapper/mapper.go index d7e611d77..3d65fc16f 100644 --- a/internal/template/mapper/mapper.go +++ b/internal/template/mapper/mapper.go @@ -5,7 +5,6 @@ package mapper import ( - "errors" "fmt" "os" "strings" @@ -16,8 +15,6 @@ const ( File = "file" ) -var errUnknownKey = errors.New("unknown key") - type Mapper struct { context map[string]map[any]any } @@ -134,7 +131,7 @@ func ParseDynamicValue(val any) (string, error) { return content, nil default: - return "", fmt.Errorf("%w %s", errUnknownKey, source) + return strVal, nil } } From e41ffbe5842a8b81080d64cbfa466244ebe1973d Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 21 Mar 2023 11:12:52 +0100 Subject: [PATCH 179/383] feat: added kubectl to tfvar in distribution --- .../kfd/v1alpha2/eks/create/distribution.go | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 78962c535..cef1074e8 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -5,6 +5,7 @@ package create import ( + "bytes" "encoding/json" "errors" "fmt" @@ -23,6 +24,7 @@ import ( "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/terraform" + bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" yamlx "github.com/sighupio/furyctl/internal/x/yaml" @@ -163,6 +165,10 @@ func (d *Distribution) Exec() error { return err } + if err := d.createTfVars(); err != nil { + return err + } + if err := d.CreateFolderStructure(); err != nil { return fmt.Errorf("error creating distribution phase folder structure: %w", err) } @@ -312,6 +318,28 @@ func (d *Distribution) injectDataPreTf(fMerger *merge.Merger) (*merge.Merger, er return merger, nil } +func (d *Distribution) createTfVars() error { + var buffer bytes.Buffer + + err := bytesx.SafeWriteToBuffer( + &buffer, + "kubectl_path = \"%s\"\n", + d.KubectlPath, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + targetTfVars := path.Join(d.Path, "terraform", "main.auto.tfvars") + + err = os.WriteFile(targetTfVars, buffer.Bytes(), iox.FullRWPermAccess) + if err != nil { + return fmt.Errorf("error writing terraform vars file: %w", err) + } + + return nil +} + func (d *Distribution) extractVpcIDFromPrevPhases(fMerger *merge.Merger) (string, error) { vpcID := "" From 55b9b506a3bda20b7cb959dc819c04dac71a3161 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 21 Mar 2023 11:22:39 +0100 Subject: [PATCH 180/383] fix: removed legacy source/ dir --- internal/distribution/iac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/distribution/iac.go b/internal/distribution/iac.go index 2cd9803a6..078bd311e 100644 --- a/internal/distribution/iac.go +++ b/internal/distribution/iac.go @@ -85,7 +85,7 @@ func (m *IACBuilder) Build() error { return fmt.Errorf("error merging files: %w", err) } - tmplCfg, err := template.NewConfig(reverseMerger, reverseMerger, []string{"source/terraform", ".gitignore"}) + tmplCfg, err := template.NewConfig(reverseMerger, reverseMerger, []string{"terraform", ".gitignore"}) if err != nil { return fmt.Errorf("error creating template config: %w", err) } From c2d00bfa811ede37372c3f1027de1ec1489a61d8 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 27 Mar 2023 16:22:48 +0200 Subject: [PATCH 181/383] feat: moved core dns patch to kubernetes phase --- .../provisioners/cluster/eks/coredns.tf.tpl | 54 +++++++++ configs/provisioners/cluster/eks/variables.tf | 6 + .../kfd/v1alpha2/eks/create/distribution.go | 28 ----- .../kfd/v1alpha2/eks/create/kubernetes.go | 107 +++++++++++++++++- 4 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 configs/provisioners/cluster/eks/coredns.tf.tpl diff --git a/configs/provisioners/cluster/eks/coredns.tf.tpl b/configs/provisioners/cluster/eks/coredns.tf.tpl new file mode 100644 index 000000000..b1fa9a033 --- /dev/null +++ b/configs/provisioners/cluster/eks/coredns.tf.tpl @@ -0,0 +1,54 @@ +locals { + coredns_scheduling_patch = { + spec = { + template = { + spec = { + nodeSelector = {{ if hasKeyAny .distribution "nodeSelector" }}{ + {{- range $key, $value := .distribution.nodeSelector }} + "{{ $key }}" = "{{ $value }}" + {{- end }} + } {{ else }} null {{ end }} + tolerations = [ + {{- range $key, $value := .distribution.tolerations }} + { + key = "{{ $value.key }}" + value = "{{ $value.value }}" + effect = "{{ $value.effect }}" + }, + {{- end }} + ] + } + } + } + } + coredns_scheduling_patch_as_json = jsonencode(local.coredns_scheduling_patch) +} + +resource "local_file" "cluster_ca" { + depends_on = [ + module.fury + ] + + content = base64decode(data.aws_eks_cluster.fury.certificate_authority.0.data) + filename = "${path.module}/secrets/ca.crt" +} + +resource "null_resource" "patch_coredns" { + depends_on = [ + module.fury, + local_file.cluster_ca + ] + + triggers = { + run_once = local.coredns_scheduling_patch_as_json + } + + provisioner "local-exec" { + command = <<-EOT + ${var.kubectl_path} patch deployment/coredns -n kube-system -p '${local.coredns_scheduling_patch_as_json}' \ + --server=${data.aws_eks_cluster.fury.endpoint} \ + --token=${data.aws_eks_cluster_auth.fury.token} \ + --certificate-authority=${path.module}/secrets/ca.crt \ + EOT + } +} diff --git a/configs/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf index 32ea9eee7..b91142fd4 100644 --- a/configs/provisioners/cluster/eks/variables.tf +++ b/configs/provisioners/cluster/eks/variables.tf @@ -135,3 +135,9 @@ variable "eks_map_users" { # ] default = [] } + +variable "kubectl_path" { + type = string + description = "The path to the kubectl binary. By default, the one present in PATH is used" + default = "kubectl" +} diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index cef1074e8..78962c535 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -5,7 +5,6 @@ package create import ( - "bytes" "encoding/json" "errors" "fmt" @@ -24,7 +23,6 @@ import ( "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/terraform" - bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" yamlx "github.com/sighupio/furyctl/internal/x/yaml" @@ -165,10 +163,6 @@ func (d *Distribution) Exec() error { return err } - if err := d.createTfVars(); err != nil { - return err - } - if err := d.CreateFolderStructure(); err != nil { return fmt.Errorf("error creating distribution phase folder structure: %w", err) } @@ -318,28 +312,6 @@ func (d *Distribution) injectDataPreTf(fMerger *merge.Merger) (*merge.Merger, er return merger, nil } -func (d *Distribution) createTfVars() error { - var buffer bytes.Buffer - - err := bytesx.SafeWriteToBuffer( - &buffer, - "kubectl_path = \"%s\"\n", - d.KubectlPath, - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - targetTfVars := path.Join(d.Path, "terraform", "main.auto.tfvars") - - err = os.WriteFile(targetTfVars, buffer.Bytes(), iox.FullRWPermAccess) - if err != nil { - return fmt.Errorf("error writing terraform vars file: %w", err) - } - - return nil -} - func (d *Distribution) extractVpcIDFromPrevPhases(fMerger *merge.Merger) (string, error) { vpcID := "" diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 1c1d78742..3aabe9de9 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -23,6 +23,7 @@ import ( "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" @@ -31,6 +32,7 @@ import ( iox "github.com/sighupio/furyctl/internal/x/io" kubex "github.com/sighupio/furyctl/internal/x/kube" netx "github.com/sighupio/furyctl/internal/x/net" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) var ( @@ -60,6 +62,8 @@ type Kubernetes struct { furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD infraOutputsPath string + distroPath string + furyctlConfPath string tfRunner *terraform.Runner awsRunner *awscli.Runner dryRun bool @@ -84,6 +88,8 @@ func NewKubernetes( furyctlConf: furyctlConf, kfdManifest: kfdManifest, infraOutputsPath: infraOutputsPath, + distroPath: paths.DistroPath, + furyctlConfPath: paths.ConfigPath, tfRunner: terraform.NewRunner( execx.NewStdExecutor(), terraform.Paths{ @@ -116,7 +122,12 @@ func (k *Kubernetes) Exec() error { return fmt.Errorf("error creating kubernetes phase folder: %w", err) } - if err := k.copyFromTemplate(); err != nil { + cfg, err := k.mergeConfig() + if err != nil { + return fmt.Errorf("error merging furyctl configuration: %w", err) + } + + if err := k.copyFromTemplate(cfg); err != nil { return err } @@ -186,7 +197,38 @@ func (k *Kubernetes) Exec() error { return nil } -func (k *Kubernetes) copyFromTemplate() error { +func (*Kubernetes) getCommonDataFromDistribution(furyctlCfg template.Config) (map[any]any, []any, error) { + var nodeSelector map[any]any + + var tolerations []any + + var ok bool + + model := merge.NewDefaultModel(furyctlCfg.Data["spec"], ".distribution.common") + + commonData, err := model.Get() + if err != nil { + return nodeSelector, tolerations, fmt.Errorf("error getting common data from distribution: %w", err) + } + + if commonData["nodeSelector"] != nil { + nodeSelector, ok = commonData["nodeSelector"].(map[any]any) + if !ok { + return nodeSelector, tolerations, fmt.Errorf("error getting nodeSelector from distribution: %w", err) + } + } + + if commonData["tolerations"] != nil { + tolerations, ok = commonData["tolerations"].([]any) + if !ok { + return nodeSelector, tolerations, fmt.Errorf("error getting tolerations from distribution: %w", err) + } + } + + return nodeSelector, tolerations, nil +} + +func (k *Kubernetes) copyFromTemplate(furyctlCfg template.Config) error { var cfg template.Config tmpFolder, err := os.MkdirTemp("", "furyctl-kube-configs-") @@ -211,6 +253,11 @@ func (k *Kubernetes) copyFromTemplate() error { eksInstallerPath := path.Join(k.Path, "..", "vendor", "installers", "eks", "modules", "eks") + nodeSelector, tolerations, err := k.getCommonDataFromDistribution(furyctlCfg) + if err != nil { + return err + } + tfConfVars := map[string]map[any]any{ "spec": { "region": k.furyctlConf.Spec.Region, @@ -220,6 +267,10 @@ func (k *Kubernetes) copyFromTemplate() error { "installerPath": eksInstallerPath, "tfVersion": k.kfdManifest.Tools.Common.Terraform.Version, }, + "distribution": { + "nodeSelector": nodeSelector, + "tolerations": tolerations, + }, "terraform": { "backend": map[string]any{ "s3": map[string]any{ @@ -246,6 +297,49 @@ func (k *Kubernetes) copyFromTemplate() error { return nil } +func (k *Kubernetes) mergeConfig() (template.Config, error) { + var cfg template.Config + + defaultsFilePath := path.Join(k.distroPath, "furyctl-defaults.yaml") + + defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) + if err != nil { + return cfg, fmt.Errorf("%s - %w", defaultsFilePath, err) + } + + furyctlConf, err := yamlx.FromFileV2[map[any]any](k.furyctlConfPath) + if err != nil { + return cfg, fmt.Errorf("%s - %w", k.furyctlConfPath, err) + } + + merger := merge.NewMerger( + merge.NewDefaultModel(defaultsFile, ".data"), + merge.NewDefaultModel(furyctlConf, ".spec.distribution"), + ) + + _, err = merger.Merge() + if err != nil { + return cfg, fmt.Errorf("error merging files: %w", err) + } + + reverseMerger := merge.NewMerger( + *merger.GetCustom(), + *merger.GetBase(), + ) + + _, err = reverseMerger.Merge() + if err != nil { + return cfg, fmt.Errorf("error merging files: %w", err) + } + + cfg, err = template.NewConfig(reverseMerger, reverseMerger, []string{"terraform", ".gitignore"}) + if err != nil { + return cfg, fmt.Errorf("error creating template config: %w", err) + } + + return cfg, nil +} + //nolint:gocyclo,maintidx,funlen // it will be refactored func (k *Kubernetes) createTfVars() error { var buffer bytes.Buffer @@ -317,6 +411,15 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + err = bytesx.SafeWriteToBuffer( + &buffer, + "kubectl_path = \"%s\"\n", + k.KubectlPath, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + err = bytesx.SafeWriteToBuffer( &buffer, "cluster_version = \"%v\"\n", From d84502f2b13881b5880f18b6cb02c90be778a69d Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 27 Mar 2023 16:35:32 +0200 Subject: [PATCH 182/383] chore: better format on coredns.tf.tpl --- configs/provisioners/cluster/eks/coredns.tf.tpl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/configs/provisioners/cluster/eks/coredns.tf.tpl b/configs/provisioners/cluster/eks/coredns.tf.tpl index b1fa9a033..12c2d4ac5 100644 --- a/configs/provisioners/cluster/eks/coredns.tf.tpl +++ b/configs/provisioners/cluster/eks/coredns.tf.tpl @@ -3,11 +3,14 @@ locals { spec = { template = { spec = { - nodeSelector = {{ if hasKeyAny .distribution "nodeSelector" }}{ + nodeSelector = + {{- if hasKeyAny .distribution "nodeSelector" }} { {{- range $key, $value := .distribution.nodeSelector }} "{{ $key }}" = "{{ $value }}" {{- end }} - } {{ else }} null {{ end }} + } + {{- else }} null + {{- end }} tolerations = [ {{- range $key, $value := .distribution.tolerations }} { From 98d41c5850399f59c9e777669519fde0b4c04f27 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 28 Mar 2023 12:22:27 +0200 Subject: [PATCH 183/383] chore: apply suggestions from code review Co-authored-by: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> --- configs/provisioners/cluster/eks/coredns.tf.tpl | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/configs/provisioners/cluster/eks/coredns.tf.tpl b/configs/provisioners/cluster/eks/coredns.tf.tpl index 12c2d4ac5..390ff1334 100644 --- a/configs/provisioners/cluster/eks/coredns.tf.tpl +++ b/configs/provisioners/cluster/eks/coredns.tf.tpl @@ -24,23 +24,16 @@ locals { } } } - coredns_scheduling_patch_as_json = jsonencode(local.coredns_scheduling_patch) + coredns_scheduling_patch_as_json = jsonencode(local.coredns_scheduling_patch) } resource "local_file" "cluster_ca" { - depends_on = [ - module.fury - ] content = base64decode(data.aws_eks_cluster.fury.certificate_authority.0.data) - filename = "${path.module}/secrets/ca.crt" + filename = "${path.module}/secrets/${data.aws_eks_cluster.fury.name}-ca.crt" } resource "null_resource" "patch_coredns" { - depends_on = [ - module.fury, - local_file.cluster_ca - ] triggers = { run_once = local.coredns_scheduling_patch_as_json @@ -51,7 +44,7 @@ resource "null_resource" "patch_coredns" { ${var.kubectl_path} patch deployment/coredns -n kube-system -p '${local.coredns_scheduling_patch_as_json}' \ --server=${data.aws_eks_cluster.fury.endpoint} \ --token=${data.aws_eks_cluster_auth.fury.token} \ - --certificate-authority=${path.module}/secrets/ca.crt \ - EOT + --certificate-authority=${local_file.cluster_ca.filename} \ + EOT } } From c3173cf36871f4928b88f2a7264443de3ca081d2 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:38:26 +0200 Subject: [PATCH 184/383] chore: apply suggestions from code review Co-authored-by: Claudio Beatrice --- configs/provisioners/cluster/eks/coredns.tf.tpl | 2 -- configs/provisioners/cluster/eks/variables.tf | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/configs/provisioners/cluster/eks/coredns.tf.tpl b/configs/provisioners/cluster/eks/coredns.tf.tpl index 390ff1334..75345d54e 100644 --- a/configs/provisioners/cluster/eks/coredns.tf.tpl +++ b/configs/provisioners/cluster/eks/coredns.tf.tpl @@ -28,13 +28,11 @@ locals { } resource "local_file" "cluster_ca" { - content = base64decode(data.aws_eks_cluster.fury.certificate_authority.0.data) filename = "${path.module}/secrets/${data.aws_eks_cluster.fury.name}-ca.crt" } resource "null_resource" "patch_coredns" { - triggers = { run_once = local.coredns_scheduling_patch_as_json } diff --git a/configs/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf index b91142fd4..a1246be10 100644 --- a/configs/provisioners/cluster/eks/variables.tf +++ b/configs/provisioners/cluster/eks/variables.tf @@ -139,5 +139,5 @@ variable "eks_map_users" { variable "kubectl_path" { type = string description = "The path to the kubectl binary. By default, the one present in PATH is used" - default = "kubectl" + default = "kubectl" } From 8efe4026a73851a89701a5b2fdc6b62527ecd28f Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Apr 2023 16:04:25 +0200 Subject: [PATCH 185/383] refactor(go.mod): reorder require sections --- go.mod | 20 ++++++++------------ go.sum | 8 ++------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index ef3700ef0..e3ea7e3eb 100644 --- a/go.mod +++ b/go.mod @@ -3,24 +3,14 @@ module github.com/sighupio/furyctl go 1.19 require ( + github.com/Al-Pragliola/go-version v1.6.2 github.com/Masterminds/sprig/v3 v3.2.3 github.com/briandowns/spinner v1.19.0 github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v1.0.1 - github.com/hashicorp/go-getter v1.6.2 - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.14.0 - github.com/stretchr/testify v1.8.2 - gopkg.in/yaml.v2 v2.4.0 -) - -require ( - github.com/Al-Pragliola/go-version v1.6.2 github.com/go-playground/validator/v10 v10.11.1 github.com/google/go-cmp v0.5.9 + github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/terraform-json v0.14.0 github.com/miekg/dns v1.1.50 github.com/onsi/ginkgo/v2 v2.6.1 @@ -28,7 +18,12 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.6.1 + github.com/spf13/viper v1.14.0 + github.com/stretchr/testify v1.8.2 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -56,6 +51,7 @@ require ( github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect diff --git a/go.sum b/go.sum index 0566aa02c..5c4dca0f4 100644 --- a/go.sum +++ b/go.sum @@ -193,9 +193,8 @@ github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6p github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -288,9 +287,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -300,8 +298,6 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.0 h1:CfQ7nEXNAmFuquHYCDMPYcAUCMrqxfhW6/MJCyVCeGY= -github.com/sighupio/fury-distribution v1.25.0/go.mod h1:EOx1HTa5Mz/KYItWwYp++EPxwqV90nBZ68nXSdaKaWE= github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= From f2d50f41449cf884275add8732f7a1ccf4601bc4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 10:16:02 +0200 Subject: [PATCH 186/383] feat: initial update Vpc.Vpn -> Infrastructure.Vpn --- go.mod | 2 +- go.sum | 2 + .../kfd/v1alpha2/eks/create/infrastructure.go | 48 +++++++++---------- .../kfd/v1alpha2/eks/create/kubernetes.go | 2 +- internal/apis/kfd/v1alpha2/eks/creator.go | 2 +- .../kfd/v1alpha2/eks/delete/infrastructure.go | 4 +- .../kfd/v1alpha2/eks/delete/kubernetes.go | 2 +- internal/apis/kfd/v1alpha2/eks/deleter.go | 2 +- internal/apis/kfd/v1alpha2/eks/vpn.go | 4 +- 9 files changed, 35 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index e3ea7e3eb..c9fa75e5e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 + github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index 5c4dca0f4..4e97ec85b 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:TjnYLyTTEU4Vr+WQbt2kuE5snc819hLw4YTgIqBB8PQ= +github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 761fb9f43..fa368a95d 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -231,7 +231,7 @@ func (i *Infrastructure) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + if i.furyctlConf.Spec.Infrastructure.Vpn != nil { err = i.addVpnDataToTfVars(&buffer) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -245,86 +245,86 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { err := bytesx.SafeWriteToBuffer( buffer, "vpn_subnetwork_cidr = \"%v\"\n", - i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.VpnClientsSubnetCidr, + i.furyctlConf.Spec.Infrastructure.Vpn.VpnClientsSubnetCidr, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances != nil { + if i.furyctlConf.Spec.Infrastructure.Vpn.Instances != nil { err = bytesx.SafeWriteToBuffer( buffer, "vpn_instances = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances, + *i.furyctlConf.Spec.Infrastructure.Vpn.Instances, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port != nil && *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port != 0 { + if i.furyctlConf.Spec.Infrastructure.Vpn.Port != nil && *i.furyctlConf.Spec.Infrastructure.Vpn.Port != 0 { err = bytesx.SafeWriteToBuffer( buffer, "vpn_port = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Port, + *i.furyctlConf.Spec.Infrastructure.Vpn.Port, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType != nil && - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType != "" { + if i.furyctlConf.Spec.Infrastructure.Vpn.InstanceType != nil && + *i.furyctlConf.Spec.Infrastructure.Vpn.InstanceType != "" { err = bytesx.SafeWriteToBuffer( buffer, "vpn_instance_type = \"%v\"\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.InstanceType, + *i.furyctlConf.Spec.Infrastructure.Vpn.InstanceType, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize != nil && - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize != 0 { + if i.furyctlConf.Spec.Infrastructure.Vpn.DiskSize != nil && + *i.furyctlConf.Spec.Infrastructure.Vpn.DiskSize != 0 { err = bytesx.SafeWriteToBuffer( buffer, "vpn_instance_disk_size = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DiskSize, + *i.furyctlConf.Spec.Infrastructure.Vpn.DiskSize, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName != nil && - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName != "" { + if i.furyctlConf.Spec.Infrastructure.Vpn.OperatorName != nil && + *i.furyctlConf.Spec.Infrastructure.Vpn.OperatorName != "" { err = bytesx.SafeWriteToBuffer( buffer, "vpn_operator_name = \"%v\"\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.OperatorName, + *i.furyctlConf.Spec.Infrastructure.Vpn.OperatorName, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - if i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits != nil && - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits != 0 { + if i.furyctlConf.Spec.Infrastructure.Vpn.DhParamsBits != nil && + *i.furyctlConf.Spec.Infrastructure.Vpn.DhParamsBits != 0 { err = bytesx.SafeWriteToBuffer( buffer, "vpn_dhparams_bits = %v\n", - *i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.DhParamsBits, + *i.furyctlConf.Spec.Infrastructure.Vpn.DhParamsBits, ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } - if len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.AllowedFromCidrs) != 0 { - allowedCidrs := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.AllowedFromCidrs)) + if len(i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.AllowedFromCidrs) != 0 { + allowedCidrs := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.AllowedFromCidrs)) - for i, cidr := range i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.AllowedFromCidrs { + for i, cidr := range i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.AllowedFromCidrs { allowedCidrs[i] = fmt.Sprintf("\"%v\"", cidr) } @@ -338,10 +338,10 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { } } - if len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.GithubUsersName) != 0 { - githubUsers := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.GithubUsersName)) + if len(i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.GithubUsersName) != 0 { + githubUsers := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.GithubUsersName)) - for i, gu := range i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Ssh.GithubUsersName { + for i, gu := range i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.GithubUsersName { githubUsers[i] = fmt.Sprintf("\"%v\"", gu) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 3aabe9de9..a2a872185 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -157,7 +157,7 @@ func (k *Kubernetes) Exec() error { logrus.Debugf("error checking VPC connection: %v", err) if k.furyctlConf.Spec.Infrastructure != nil { - if k.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + if k.furyctlConf.Spec.Infrastructure.Vpn != nil { return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) } } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 7f4ecbbd6..14d71d608 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -119,7 +119,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { v.kfdManifest.Tools.Common.Furyagent.Version, v.vpnAutoConnect, v.skipVpn, - v.furyctlConf.Spec.Infrastructure.Vpc.Vpn, + v.furyctlConf.Spec.Infrastructure.Vpn, ) switch v.phase { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index a5f737feb..37068ba11 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -111,12 +111,12 @@ func (i *Infrastructure) isVpnConfigured() bool { return false } - vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn + vpn := i.furyctlConf.Spec.Infrastructure.Vpn if vpn == nil { return false } - instances := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances + instances := i.furyctlConf.Spec.Infrastructure.Vpn.Instances if instances == nil { return true } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 1d8fa684e..62525dd48 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -106,7 +106,7 @@ func (k *Kubernetes) Exec() error { logrus.Debugf("error checking VPC connection: %v", err) if k.furyctlConf.Spec.Infrastructure != nil { - if k.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil { + if k.furyctlConf.Spec.Infrastructure.Vpn != nil { return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) } } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 429c63cde..8c6d37ca7 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -106,7 +106,7 @@ func (d *ClusterDeleter) Delete() error { d.kfdManifest.Tools.Common.Furyagent.Version, d.vpnAutoConnect, d.skipVpn, - d.furyctlConf.Spec.Infrastructure.Vpc.Vpn, + d.furyctlConf.Spec.Infrastructure.Vpn, ) switch d.phase { diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 379f8f283..4d41e9cbe 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -35,7 +35,7 @@ type VpnConnector struct { certDir string autoconnect bool skip bool - config *private.SpecInfrastructureVpcVpn + config *private.SpecInfrastructureVpn ovRunner *openvpn.Runner faRunner *furyagent.Runner } @@ -47,7 +47,7 @@ func NewVpnConnector( faVersion string, autoconnect, skip bool, - config *private.SpecInfrastructureVpcVpn, + config *private.SpecInfrastructureVpn, ) *VpnConnector { executor := execx.NewStdExecutor() From 59be7a5da94414c481c80be3c8dba88859598b2a Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 10:26:17 +0200 Subject: [PATCH 187/383] fix: vpn in tests schema --- .../dependencies/tools/test_data/furyctl.yaml | 18 +++++------ .../data/e2e/create/cluster/data/furyctl.yaml | 30 +++++++++---------- .../e2e/validate/config/correct/furyctl.yaml | 30 +++++++++---------- .../create-complete/data/furyctl.yaml | 30 +++++++++---------- .../create-skip-infra/data/furyctl.yaml | 30 +++++++++---------- .../create-skip-kube/data/furyctl.yaml | 30 +++++++++---------- 6 files changed, 84 insertions(+), 84 deletions(-) diff --git a/internal/dependencies/tools/test_data/furyctl.yaml b/internal/dependencies/tools/test_data/furyctl.yaml index e7c6edd94..4132d43de 100644 --- a/internal/dependencies/tools/test_data/furyctl.yaml +++ b/internal/dependencies/tools/test_data/furyctl.yaml @@ -32,15 +32,15 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" diff --git a/test/data/e2e/create/cluster/data/furyctl.yaml b/test/data/e2e/create/cluster/data/furyctl.yaml index 8981a8413..01eacf22e 100644 --- a/test/data/e2e/create/cluster/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/data/furyctl.yaml @@ -32,21 +32,21 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - instances: 1 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index 8981a8413..01eacf22e 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -32,21 +32,21 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - instances: 1 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" diff --git a/test/data/expensive/create-complete/data/furyctl.yaml b/test/data/expensive/create-complete/data/furyctl.yaml index 73536c9d9..87305be5e 100644 --- a/test/data/expensive/create-complete/data/furyctl.yaml +++ b/test/data/expensive/create-complete/data/furyctl.yaml @@ -32,21 +32,21 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - instances: 1 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" diff --git a/test/data/expensive/create-skip-infra/data/furyctl.yaml b/test/data/expensive/create-skip-infra/data/furyctl.yaml index 6f6b10ded..312de7995 100644 --- a/test/data/expensive/create-skip-infra/data/furyctl.yaml +++ b/test/data/expensive/create-skip-infra/data/furyctl.yaml @@ -32,21 +32,21 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - instances: 1 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" diff --git a/test/data/expensive/create-skip-kube/data/furyctl.yaml b/test/data/expensive/create-skip-kube/data/furyctl.yaml index c169e96c1..ce8f3aeef 100644 --- a/test/data/expensive/create-skip-kube/data/furyctl.yaml +++ b/test/data/expensive/create-skip-kube/data/furyctl.yaml @@ -32,21 +32,21 @@ spec: - 10.0.20.0/24 - 10.0.30.0/24 - 10.0.40.0/24 - vpn: - instances: 1 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 192.168.200.0/24 - ssh: - publicKeys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" - githubUsersName: - - Al-Pragliola - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 1 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 192.168.200.0/24 + ssh: + publicKeys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" + githubUsersName: + - Al-Pragliola + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" From a17d6ecdf3069441d2fc8f8987269ab09408fd48 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 10:32:51 +0200 Subject: [PATCH 188/383] fix: linting & tests --- internal/apis/kfd/v1alpha2/eks/tool.go | 4 +- .../config/default/data/expected-furyctl.yaml | 52 +- .../dependencies/v1.24.1/furyctl.yaml | 32 +- .../template/complex-dry-run/furyctl.yaml | 48 +- .../e2e/dump/template/complex/furyctl.yaml | 48 +- .../public/ekscluster-kfd-v1alpha2.json | 614 ++++++++++++++--- .../e2e/validate/config/nodistro/furyctl.yaml | 46 +- .../e2e/validate/config/wrong/furyctl.yaml | 46 +- .../public/ekscluster-kfd-v1alpha2.json | 629 ++++++++++++++---- .../dependencies/correct/furyctl.yaml | 46 +- .../dependencies/missing/furyctl.yaml | 46 +- .../validate/dependencies/wrong/furyctl.yaml | 46 +- test/data/integration/v1.24.1/furyctl.yaml | 32 +- 13 files changed, 1247 insertions(+), 442 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index 3f29945c6..8116e0d39 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -46,8 +46,8 @@ func (x *ExtraToolsValidator) Validate(confPath string) ([]string, []error) { } func (x *ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { - if conf.Spec.Infrastructure.Vpc != nil && - conf.Spec.Infrastructure.Vpc.Vpn != nil { + if conf.Spec.Infrastructure != nil && + conf.Spec.Infrastructure.Vpn != nil { oRunner := openvpn.NewRunner(x.executor, openvpn.Paths{ Openvpn: "openvpn", }) diff --git a/test/data/e2e/create/config/default/data/expected-furyctl.yaml b/test/data/e2e/create/config/default/data/expected-furyctl.yaml index 1835270d4..33ed1f6f5 100644 --- a/test/data/e2e/create/config/default/data/expected-furyctl.yaml +++ b/test/data/e2e/create/config/default/data/expected-furyctl.yaml @@ -47,32 +47,32 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - # This section defines the creation of VPN bastions - vpn: - # The number of instance to create, 0 to skip the creation - instances: 2 - # The port used by the OpenVPN server - port: 1194 - # The size of the AWS ec2 instance - instanceType: t3.micro - # The size of the disk in GB - diskSize: 50 - # The username of the account to create in the bastion's operating system - operatorName: sighup - # The dhParamsBits size used for the creation of the .pem file that will be used in the dh openvpn server.conf file - dhParamsBits: 2048 - # The CIDR that will be used to assign IP addresses to the VPN clients when connected - vpnClientsSubnetCidr: 172.16.0.0/16 - # ssh access settings - ssh: - # Not yet supported - publicKeys: [] - # The github user name list that will be used to get the ssh public key that will be added as authorized key to the operatorName user - githubUsersName: - - johndoe - # The CIDR enabled in the security group that can access the bastions in SSH - allowedFromCidrs: - - 0.0.0.0/0 + # This section defines the creation of VPN bastions + vpn: + # The number of instance to create, 0 to skip the creation + instances: 2 + # The port used by the OpenVPN server + port: 1194 + # The size of the AWS ec2 instance + instanceType: t3.micro + # The size of the disk in GB + diskSize: 50 + # The username of the account to create in the bastion's operating system + operatorName: sighup + # The dhParamsBits size used for the creation of the .pem file that will be used in the dh openvpn server.conf file + dhParamsBits: 2048 + # The CIDR that will be used to assign IP addresses to the VPN clients when connected + vpnClientsSubnetCidr: 172.16.0.0/16 + # ssh access settings + ssh: + # Not yet supported + publicKeys: [] + # The github user name list that will be used to get the ssh public key that will be added as authorized key to the operatorName user + githubUsersName: + - johndoe + # The CIDR enabled in the security group that can access the bastions in SSH + allowedFromCidrs: + - 0.0.0.0/0 # This section describes how the EKS cluster will be created kubernetes: # This key contains the ssh public key that can connect to the nodes via SSH using the ec2-user user diff --git a/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml b/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml index 5e1e6fde9..e8fcaf938 100644 --- a/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml +++ b/test/data/e2e/download/dependencies/v1.24.1/furyctl.yaml @@ -31,22 +31,22 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 172.16.0.0/16 - ssh: - publicKeys: - - "ssh-ed25519 XYX" - - "{file://relative/path/to/ssh.pub}" - githubUsersName: - - lnovara - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 172.16.0.0/16 + ssh: + publicKeys: + - "ssh-ed25519 XYX" + - "{file://relative/path/to/ssh.pub}" + githubUsersName: + - lnovara + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: vpcId: vpc-0f92da9b4a2089963 subnetIds: diff --git a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml index f2fdc92f4..b874f7dcb 100644 --- a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml @@ -41,31 +41,31 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - enabled: true - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed56778 XYX" + vpn: + # the vpn creation can be optional + enabled: true + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed56778 XYX" # - "{file://relative/path/to/ssh.pub}" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # cidr allowed to talk with the apiServer apiServerAllowedCidrs: diff --git a/test/data/e2e/dump/template/complex/furyctl.yaml b/test/data/e2e/dump/template/complex/furyctl.yaml index 2c3c8b9cc..79ad667dc 100644 --- a/test/data/e2e/dump/template/complex/furyctl.yaml +++ b/test/data/e2e/dump/template/complex/furyctl.yaml @@ -41,31 +41,31 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - enabled: true - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed56778 XYX" + vpn: + # the vpn creation can be optional + enabled: true + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed56778 XYX" # - "{file://relative/path/to/ssh.pub}" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # cidr allowed to talk with the apiServer apiServerAllowedCidrs: diff --git a/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json index 3637ffec6..76417d576 100644 --- a/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2-public.json", "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", "type": "object", "properties": { @@ -122,7 +122,167 @@ } } }, - + "Spec.Distribution.CustomPatches": { + "type": "object", + "properties": { + "configMapGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ConfigMapGenerator" + }, + "secretGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.SecretGenerator" + }, + "patches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patches" + }, + "patchesStrategicMerge": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.PatchesStrategicMerge" + } + } + }, + "Spec.Distribution.CustomPatches.ConfigMapGenerator": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" + } + }, + "Spec.Distribution.CustomPatches.SecretGenerator": { + "type": "array", + "items": { + "allOf": [{ "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" }], + "properties": { + "type": { + "type": "string" + } + } + } + }, + "Spec.Distribution.CustomPatches.ResourceGenerator": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "behavior": { + "type": "string", + "enum": ["create", "replace", "merge"] + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + }, + "envs": { + "type": "array", + "items": { + "type": "string" + } + }, + "literals": { + "type": "array", + "items": { + "type": "string" + } + }, + "namespace": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "annotations": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "disableNameSuffixHash": { + "type": "boolean" + }, + "immutable": { + "type": "boolean" + } + } + } + }, + "required": ["name"] + }, + "Spec.Distribution.CustomPatches.Patches": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch" + } + }, + "Spec.Distribution.CustomPatches.PatchesStrategicMerge": { + "type": "array", + "items": { + "type": "string" + } + }, + "Spec.Distribution.CustomPatches.Patch": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch.Target" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowNameChange": { + "type": "boolean" + }, + "allowKindChange": { + "type": "boolean" + } + } + }, + "path": { + "type": "string" + }, + "patch": { + "type": "string" + } + }, + "oneOf": [ + { + "required": ["path"] + }, + { + "required": ["patch"] + } + ] + }, + "Spec.Distribution.CustomPatches.Patch.Target": { + "type": "object", + "additionalProperties": false, + "properties": { + "group": { + "type": "string" + }, + "version": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "annotationSelector": { + "type": "string" + } + } + }, "Spec.ToolsConfiguration": { "type": "object", "additionalProperties": false, @@ -164,11 +324,10 @@ "additionalProperties": false, "properties": { "bucketName": { - "type": "string" + "$ref": "#/$defs/Types.AwsS3BucketName" }, "keyPrefix": { - "type": "string", - "maxLength": 37 + "$ref": "#/$defs/Types.AwsS3KeyPrefix" }, "region": { "$ref": "#/$defs/Types.AwsRegion" @@ -187,8 +346,81 @@ "properties": { "vpc": { "$ref": "#/$defs/Spec.Infrastructure.Vpc" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpn" } - } + }, + "allOf": [ + { + "if": { + "allOf": [ + { + "properties": { + "vpc": { + "type": "null" + } + } + }, + { + "not": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "required": ["vpcId"] + } + } + } + }, + { + "if": { + "allOf": [ + { + "not": { + "properties": { + "vpc": { + "type": "null" + } + } + } + }, + { + "not": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] }, "Spec.Infrastructure.Vpc": { "type": "object", @@ -196,9 +428,6 @@ "properties": { "network": { "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" - }, - "vpn": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" } }, "required": [ @@ -243,7 +472,7 @@ "public" ] }, - "Spec.Infrastructure.Vpc.Vpn": { + "Spec.Infrastructure.Vpn": { "type": "object", "additionalProperties": false, "properties": { @@ -269,7 +498,10 @@ "$ref": "#/$defs/Types.Cidr" }, "ssh": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + "$ref": "#/$defs/Spec.Infrastructure.Vpn.Ssh" + }, + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" } }, "required": [ @@ -277,7 +509,7 @@ "vpnClientsSubnetCidr" ] }, - "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "Spec.Infrastructure.Vpn.Ssh": { "type": "object", "additionalProperties": false, "properties": { @@ -298,7 +530,8 @@ "type": "array", "items": { "type": "string" - } + }, + "minItems": 1 }, "allowedFromCidrs": { "type": "array", @@ -333,7 +566,7 @@ "nodeAllowedSshPublicKey": { "anyOf": [ { - "$ref": "#/$defs/Types.SshPubKey" + "$ref": "#/$defs/Types.AwsSshPubKey" }, { "$ref": "#/$defs/Types.FileRef" @@ -626,6 +859,9 @@ }, "modules": { "$ref": "#/$defs/Spec.Distribution.Modules" + }, + "customPatches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches" } }, "required": [ @@ -745,6 +981,9 @@ "monitoring": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" }, + "networking": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking" + }, "policy": { "$ref": "#/$defs/Spec.Distribution.Modules.Policy" } @@ -760,7 +999,7 @@ "additionalProperties": false, "properties": { "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides" }, "baseDomain": { "type": "string" @@ -774,8 +1013,8 @@ "dns": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" }, - "externalDns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + "forecastle": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Forecastle" } }, "required": [ @@ -784,6 +1023,24 @@ "nginx" ] }, + "Spec.Distribution.Modules.Ingress.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "ingresses": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides.Ingresses" + }, + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { "type": "object", "additionalProperties": false, @@ -793,6 +1050,15 @@ } } }, + "Spec.Distribution.Modules.Ingress.Forecastle": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Ingress.Nginx": { "type": "object", "additionalProperties": false, @@ -803,6 +1069,9 @@ }, "tls": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -861,6 +1130,9 @@ "properties": { "clusterIssuer": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -881,9 +1153,6 @@ "type": { "type": "string", "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" } }, "required": [ @@ -892,42 +1161,6 @@ "email" ] }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.ExternalDNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "privateIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "publicIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "privateIamRoleArn", - "publicIamRoleArn" - ] - }, "Spec.Distribution.Modules.Ingress.DNS": { "type": "object", "additionalProperties": false, @@ -937,6 +1170,9 @@ }, "private": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -969,29 +1205,54 @@ }, "create": { "type": "boolean" - }, - "vpcId": { - "type": "string" } }, "required": [ "name", - "create", - "vpcId" + "create" ] }, "Spec.Distribution.Modules.Logging": { "type": "object", "additionalProperties": false, "properties": { + "type": { + "type": "string", + "enum": ["opensearch", "loki"] + }, "overrides": { "$ref": "#/$defs/Types.FuryModuleOverrides" }, "opensearch": { "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + }, + "loki": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Loki" + }, + "cerebro": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Cerebro" + }, + "minio": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Minio" + }, + "operator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Operator" } }, - "required": ["opensearch"] + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "opensearch" + } + } + }, + "then": { + "required": ["opensearch"] + } + } + ] }, "Spec.Distribution.Modules.Logging.Opensearch": { "type": "object", @@ -1006,12 +1267,54 @@ }, "storageSize": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ "type" ] }, + "Spec.Distribution.Modules.Logging.Cerebro": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Minio": { + "type": "object", + "additionalProperties": false, + "properties": { + "storageSize": { + "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Loki": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Logging.Operator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Monitoring": { "type": "object", "additionalProperties": false, @@ -1024,6 +1327,18 @@ }, "alertmanager": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + }, + "grafana": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Grafana" + }, + "blackboxExporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.BlackboxExporter" + }, + "kubeStateMetrics": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.KubeStateMetrics" + }, + "x509Exporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.X509Exporter" } } }, @@ -1057,6 +1372,63 @@ } } }, + "Spec.Distribution.Modules.Monitoring.Grafana": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.BlackboxExporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.KubeStateMetrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.X509Exporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Networking": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + }, + "tigeraOperator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking.TigeraOperator" + } + } + }, + "Spec.Distribution.Modules.Networking.TigeraOperator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Policy": { "type": "object", "additionalProperties": false, @@ -1078,6 +1450,9 @@ "items": { "type": "string" } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } } }, @@ -1100,6 +1475,9 @@ "properties": { "eks": { "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["eks"] @@ -1112,16 +1490,13 @@ "$ref": "#/$defs/Types.AwsRegion" }, "bucketName": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "$ref": "#/$defs/Types.AwsS3BucketName", + "maxLength": 49 } }, "required": [ "region", - "bucketName", - "iamRoleArn" + "bucketName" ] }, "Spec.Distribution.Modules.Auth": { @@ -1159,11 +1534,7 @@ } }, "then": { - "properties": { - "auth": { - "required": ["dex", "pomerium", "baseDomain"] - } - } + "required": ["dex", "pomerium", "baseDomain"] }, "else": { "properties": { @@ -1285,6 +1656,9 @@ }, "policy": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -1318,6 +1692,9 @@ "properties": { "connectors": { "type": "array" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["connectors"] @@ -1326,52 +1703,48 @@ "type": "object", "additionalProperties": false, "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } }, "ebsCsiDriver": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } }, "loadBalancerController": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } + }, + "ebsSnapshotController": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" } } }, - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "iamRoleArn" - ] - }, - "Types.SemVer": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" @@ -1441,6 +1814,10 @@ "type": "string", "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" }, + "Types.AwsSshPubKey": { + "type": "string", + "pattern": "^ssh\\-(ed25519|rsa)\\s+" + }, "Types.AwsSubnetId": { "type": "string", "pattern": "^subnet\\-[0-9a-f]{17}$" @@ -1454,6 +1831,24 @@ "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" }, + "Types.AwsS3BucketName": { + "type": "string", + "allOf": [ + { + "pattern": "^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$" + }, + { + "not": { + "pattern": "^xn--|-s3alias$" + } + } + ] + }, + "Types.AwsS3KeyPrefix": { + "type": "string", + "pattern": "^[A-z0-9][A-z0-9!-_.*'()]+$", + "maxLength": 960 + }, "Types.KubeLabels": { "type": "object", "additionalProperties": { "type": "string" } @@ -1541,6 +1936,21 @@ } } }, + "Types.FuryModuleComponentOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Types.FuryModuleOverridesIngress": { "type": "object", "additionalProperties": false, diff --git a/test/data/e2e/validate/config/nodistro/furyctl.yaml b/test/data/e2e/validate/config/nodistro/furyctl.yaml index e86abf55c..97b71346c 100644 --- a/test/data/e2e/validate/config/nodistro/furyctl.yaml +++ b/test/data/e2e/validate/config/nodistro/furyctl.yaml @@ -43,29 +43,29 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed25519 XYX" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase # and will be automatically gathered from the output of the bootstrap phase diff --git a/test/data/e2e/validate/config/wrong/furyctl.yaml b/test/data/e2e/validate/config/wrong/furyctl.yaml index 9093211dc..4537ef477 100644 --- a/test/data/e2e/validate/config/wrong/furyctl.yaml +++ b/test/data/e2e/validate/config/wrong/furyctl.yaml @@ -44,29 +44,29 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed25519 XYX" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase # and will be automatically gathered from the output of the bootstrap phase diff --git a/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json index 756a63d49..76417d576 100644 --- a/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2-public.json", "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", "type": "object", "properties": { @@ -93,21 +93,6 @@ } } } - }, - { - "properties": { - "infrastructure": { - "properties": { - "vpc": { - "properties": { - "vpn": { - "type": "null" - } - } - } - } - } - } } ] }, @@ -137,7 +122,167 @@ } } }, - + "Spec.Distribution.CustomPatches": { + "type": "object", + "properties": { + "configMapGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ConfigMapGenerator" + }, + "secretGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.SecretGenerator" + }, + "patches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patches" + }, + "patchesStrategicMerge": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.PatchesStrategicMerge" + } + } + }, + "Spec.Distribution.CustomPatches.ConfigMapGenerator": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" + } + }, + "Spec.Distribution.CustomPatches.SecretGenerator": { + "type": "array", + "items": { + "allOf": [{ "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" }], + "properties": { + "type": { + "type": "string" + } + } + } + }, + "Spec.Distribution.CustomPatches.ResourceGenerator": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "behavior": { + "type": "string", + "enum": ["create", "replace", "merge"] + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + }, + "envs": { + "type": "array", + "items": { + "type": "string" + } + }, + "literals": { + "type": "array", + "items": { + "type": "string" + } + }, + "namespace": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "annotations": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "disableNameSuffixHash": { + "type": "boolean" + }, + "immutable": { + "type": "boolean" + } + } + } + }, + "required": ["name"] + }, + "Spec.Distribution.CustomPatches.Patches": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch" + } + }, + "Spec.Distribution.CustomPatches.PatchesStrategicMerge": { + "type": "array", + "items": { + "type": "string" + } + }, + "Spec.Distribution.CustomPatches.Patch": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch.Target" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowNameChange": { + "type": "boolean" + }, + "allowKindChange": { + "type": "boolean" + } + } + }, + "path": { + "type": "string" + }, + "patch": { + "type": "string" + } + }, + "oneOf": [ + { + "required": ["path"] + }, + { + "required": ["patch"] + } + ] + }, + "Spec.Distribution.CustomPatches.Patch.Target": { + "type": "object", + "additionalProperties": false, + "properties": { + "group": { + "type": "string" + }, + "version": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "annotationSelector": { + "type": "string" + } + } + }, "Spec.ToolsConfiguration": { "type": "object", "additionalProperties": false, @@ -179,11 +324,10 @@ "additionalProperties": false, "properties": { "bucketName": { - "type": "string" + "$ref": "#/$defs/Types.AwsS3BucketName" }, "keyPrefix": { - "type": "string", - "maxLength": 37 + "$ref": "#/$defs/Types.AwsS3KeyPrefix" }, "region": { "$ref": "#/$defs/Types.AwsRegion" @@ -202,8 +346,81 @@ "properties": { "vpc": { "$ref": "#/$defs/Spec.Infrastructure.Vpc" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpn" } - } + }, + "allOf": [ + { + "if": { + "allOf": [ + { + "properties": { + "vpc": { + "type": "null" + } + } + }, + { + "not": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "required": ["vpcId"] + } + } + } + }, + { + "if": { + "allOf": [ + { + "not": { + "properties": { + "vpc": { + "type": "null" + } + } + } + }, + { + "not": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] }, "Spec.Infrastructure.Vpc": { "type": "object", @@ -211,9 +428,6 @@ "properties": { "network": { "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" - }, - "vpn": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" } }, "required": [ @@ -258,7 +472,7 @@ "public" ] }, - "Spec.Infrastructure.Vpc.Vpn": { + "Spec.Infrastructure.Vpn": { "type": "object", "additionalProperties": false, "properties": { @@ -284,7 +498,10 @@ "$ref": "#/$defs/Types.Cidr" }, "ssh": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + "$ref": "#/$defs/Spec.Infrastructure.Vpn.Ssh" + }, + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" } }, "required": [ @@ -292,7 +509,7 @@ "vpnClientsSubnetCidr" ] }, - "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "Spec.Infrastructure.Vpn.Ssh": { "type": "object", "additionalProperties": false, "properties": { @@ -313,7 +530,8 @@ "type": "array", "items": { "type": "string" - } + }, + "minItems": 1 }, "allowedFromCidrs": { "type": "array", @@ -348,7 +566,7 @@ "nodeAllowedSshPublicKey": { "anyOf": [ { - "$ref": "#/$defs/Types.SshPubKey" + "$ref": "#/$defs/Types.AwsSshPubKey" }, { "$ref": "#/$defs/Types.FileRef" @@ -641,6 +859,9 @@ }, "modules": { "$ref": "#/$defs/Spec.Distribution.Modules" + }, + "customPatches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches" } }, "required": [ @@ -760,6 +981,9 @@ "monitoring": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" }, + "networking": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking" + }, "policy": { "$ref": "#/$defs/Spec.Distribution.Modules.Policy" } @@ -775,7 +999,7 @@ "additionalProperties": false, "properties": { "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides" }, "baseDomain": { "type": "string" @@ -789,8 +1013,8 @@ "dns": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" }, - "externalDns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + "forecastle": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Forecastle" } }, "required": [ @@ -799,6 +1023,24 @@ "nginx" ] }, + "Spec.Distribution.Modules.Ingress.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "ingresses": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides.Ingresses" + }, + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { "type": "object", "additionalProperties": false, @@ -808,6 +1050,15 @@ } } }, + "Spec.Distribution.Modules.Ingress.Forecastle": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Ingress.Nginx": { "type": "object", "additionalProperties": false, @@ -818,6 +1069,9 @@ }, "tls": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -876,6 +1130,9 @@ "properties": { "clusterIssuer": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -896,9 +1153,6 @@ "type": { "type": "string", "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" } }, "required": [ @@ -907,42 +1161,6 @@ "email" ] }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.ExternalDNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "privateIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "publicIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "privateIamRoleArn", - "publicIamRoleArn" - ] - }, "Spec.Distribution.Modules.Ingress.DNS": { "type": "object", "additionalProperties": false, @@ -952,6 +1170,9 @@ }, "private": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -984,29 +1205,54 @@ }, "create": { "type": "boolean" - }, - "vpcId": { - "type": "string" } }, "required": [ "name", - "create", - "vpcId" + "create" ] }, "Spec.Distribution.Modules.Logging": { "type": "object", "additionalProperties": false, "properties": { + "type": { + "type": "string", + "enum": ["opensearch", "loki"] + }, "overrides": { "$ref": "#/$defs/Types.FuryModuleOverrides" }, "opensearch": { "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + }, + "loki": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Loki" + }, + "cerebro": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Cerebro" + }, + "minio": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Minio" + }, + "operator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Operator" } }, - "required": ["opensearch"] + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "opensearch" + } + } + }, + "then": { + "required": ["opensearch"] + } + } + ] }, "Spec.Distribution.Modules.Logging.Opensearch": { "type": "object", @@ -1021,12 +1267,54 @@ }, "storageSize": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ "type" ] }, + "Spec.Distribution.Modules.Logging.Cerebro": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Minio": { + "type": "object", + "additionalProperties": false, + "properties": { + "storageSize": { + "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Loki": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Logging.Operator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Monitoring": { "type": "object", "additionalProperties": false, @@ -1039,6 +1327,18 @@ }, "alertmanager": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + }, + "grafana": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Grafana" + }, + "blackboxExporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.BlackboxExporter" + }, + "kubeStateMetrics": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.KubeStateMetrics" + }, + "x509Exporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.X509Exporter" } } }, @@ -1072,6 +1372,63 @@ } } }, + "Spec.Distribution.Modules.Monitoring.Grafana": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.BlackboxExporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.KubeStateMetrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.X509Exporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Networking": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + }, + "tigeraOperator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking.TigeraOperator" + } + } + }, + "Spec.Distribution.Modules.Networking.TigeraOperator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Policy": { "type": "object", "additionalProperties": false, @@ -1093,6 +1450,9 @@ "items": { "type": "string" } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } } }, @@ -1115,6 +1475,9 @@ "properties": { "eks": { "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["eks"] @@ -1127,16 +1490,13 @@ "$ref": "#/$defs/Types.AwsRegion" }, "bucketName": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "$ref": "#/$defs/Types.AwsS3BucketName", + "maxLength": 49 } }, "required": [ "region", - "bucketName", - "iamRoleArn" + "bucketName" ] }, "Spec.Distribution.Modules.Auth": { @@ -1174,11 +1534,7 @@ } }, "then": { - "properties": { - "auth": { - "required": ["dex", "pomerium", "baseDomain"] - } - } + "required": ["dex", "pomerium", "baseDomain"] }, "else": { "properties": { @@ -1300,6 +1656,9 @@ }, "policy": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -1333,6 +1692,9 @@ "properties": { "connectors": { "type": "array" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["connectors"] @@ -1341,52 +1703,48 @@ "type": "object", "additionalProperties": false, "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } }, "ebsCsiDriver": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } }, "loadBalancerController": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } + }, + "ebsSnapshotController": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" } } }, - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "iamRoleArn" - ] - }, - "Types.SemVer": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" @@ -1456,6 +1814,10 @@ "type": "string", "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" }, + "Types.AwsSshPubKey": { + "type": "string", + "pattern": "^ssh\\-(ed25519|rsa)\\s+" + }, "Types.AwsSubnetId": { "type": "string", "pattern": "^subnet\\-[0-9a-f]{17}$" @@ -1469,6 +1831,24 @@ "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" }, + "Types.AwsS3BucketName": { + "type": "string", + "allOf": [ + { + "pattern": "^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$" + }, + { + "not": { + "pattern": "^xn--|-s3alias$" + } + } + ] + }, + "Types.AwsS3KeyPrefix": { + "type": "string", + "pattern": "^[A-z0-9][A-z0-9!-_.*'()]+$", + "maxLength": 960 + }, "Types.KubeLabels": { "type": "object", "additionalProperties": { "type": "string" } @@ -1556,6 +1936,21 @@ } } }, + "Types.FuryModuleComponentOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Types.FuryModuleOverridesIngress": { "type": "object", "additionalProperties": false, diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index c4613bd55..57a2e6202 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -43,29 +43,29 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed25519 XYX" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase # and will be automatically gathered from the output of the bootstrap phase diff --git a/test/data/e2e/validate/dependencies/missing/furyctl.yaml b/test/data/e2e/validate/dependencies/missing/furyctl.yaml index e86abf55c..97b71346c 100644 --- a/test/data/e2e/validate/dependencies/missing/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/missing/furyctl.yaml @@ -43,29 +43,29 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed25519 XYX" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase # and will be automatically gathered from the output of the bootstrap phase diff --git a/test/data/e2e/validate/dependencies/wrong/furyctl.yaml b/test/data/e2e/validate/dependencies/wrong/furyctl.yaml index 140417981..e06755136 100644 --- a/test/data/e2e/validate/dependencies/wrong/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/wrong/furyctl.yaml @@ -43,29 +43,29 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - # the vpn creation can be optional - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - # is it really customizable? what is this? - operatorName: sighup - # should be optional? - dhParamsBits: 2048 - # Which IP address will be given to VPN users - vpnClientsSubnetCidr: 172.16.0.0/16 - # new ssh configuration, with a more hierarchical structure - ssh: - # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal - publicKeys: - - "ssh-ed25519 XYX" - # Github users to get ssh keys from - githubUsersName: - - lnovara - # the CIDRs that are allowed for the ssh connection - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + # the vpn creation can be optional + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + # is it really customizable? what is this? + operatorName: sighup + # should be optional? + dhParamsBits: 2048 + # Which IP address will be given to VPN users + vpnClientsSubnetCidr: 172.16.0.0/16 + # new ssh configuration, with a more hierarchical structure + ssh: + # ssh public keys enabled to access the vpn instances, NOT YET IMPLEMENTED, proposal + publicKeys: + - "ssh-ed25519 XYX" + # Github users to get ssh keys from + githubUsersName: + - lnovara + # the CIDRs that are allowed for the ssh connection + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: # vpcID and subnetIDs are optional, needed only if you did not create vpc with bootstrap phase # and will be automatically gathered from the output of the bootstrap phase diff --git a/test/data/integration/v1.24.1/furyctl.yaml b/test/data/integration/v1.24.1/furyctl.yaml index 5e1e6fde9..e8fcaf938 100644 --- a/test/data/integration/v1.24.1/furyctl.yaml +++ b/test/data/integration/v1.24.1/furyctl.yaml @@ -31,22 +31,22 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - vpn: - instances: 2 - port: 1194 - instanceType: t3.micro - diskSize: 50 - operatorName: sighup - dhParamsBits: 2048 - vpnClientsSubnetCidr: 172.16.0.0/16 - ssh: - publicKeys: - - "ssh-ed25519 XYX" - - "{file://relative/path/to/ssh.pub}" - githubUsersName: - - lnovara - allowedFromCidrs: - - 0.0.0.0/0 + vpn: + instances: 2 + port: 1194 + instanceType: t3.micro + diskSize: 50 + operatorName: sighup + dhParamsBits: 2048 + vpnClientsSubnetCidr: 172.16.0.0/16 + ssh: + publicKeys: + - "ssh-ed25519 XYX" + - "{file://relative/path/to/ssh.pub}" + githubUsersName: + - lnovara + allowedFromCidrs: + - 0.0.0.0/0 kubernetes: vpcId: vpc-0f92da9b4a2089963 subnetIds: From 711031f78cd6a3504541409a20c191c677266161 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 10:41:34 +0200 Subject: [PATCH 189/383] fix: e2e tests --- .../data/e2e/create/cluster/data/furyctl.yaml | 1 - .../public/ekscluster-kfd-v1alpha2.json | 629 ++++++++++++++---- .../config/ekscluster-kfd-v1alpha2.yaml.tpl | 52 +- .../e2e/validate/config/correct/furyctl.yaml | 1 - 4 files changed, 538 insertions(+), 145 deletions(-) diff --git a/test/data/e2e/create/cluster/data/furyctl.yaml b/test/data/e2e/create/cluster/data/furyctl.yaml index 01eacf22e..9a37dc3cb 100644 --- a/test/data/e2e/create/cluster/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/data/furyctl.yaml @@ -197,7 +197,6 @@ spec: velero: eks: bucketName: example-velero - iamRoleArn: arn:aws:iam::123456789012:role/example-velero # private. region: eu-west-1 auth: provider: diff --git a/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json index 756a63d49..76417d576 100644 --- a/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2-public.json", "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", "type": "object", "properties": { @@ -93,21 +93,6 @@ } } } - }, - { - "properties": { - "infrastructure": { - "properties": { - "vpc": { - "properties": { - "vpn": { - "type": "null" - } - } - } - } - } - } } ] }, @@ -137,7 +122,167 @@ } } }, - + "Spec.Distribution.CustomPatches": { + "type": "object", + "properties": { + "configMapGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ConfigMapGenerator" + }, + "secretGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.SecretGenerator" + }, + "patches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patches" + }, + "patchesStrategicMerge": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.PatchesStrategicMerge" + } + } + }, + "Spec.Distribution.CustomPatches.ConfigMapGenerator": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" + } + }, + "Spec.Distribution.CustomPatches.SecretGenerator": { + "type": "array", + "items": { + "allOf": [{ "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" }], + "properties": { + "type": { + "type": "string" + } + } + } + }, + "Spec.Distribution.CustomPatches.ResourceGenerator": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "behavior": { + "type": "string", + "enum": ["create", "replace", "merge"] + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + }, + "envs": { + "type": "array", + "items": { + "type": "string" + } + }, + "literals": { + "type": "array", + "items": { + "type": "string" + } + }, + "namespace": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "annotations": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "disableNameSuffixHash": { + "type": "boolean" + }, + "immutable": { + "type": "boolean" + } + } + } + }, + "required": ["name"] + }, + "Spec.Distribution.CustomPatches.Patches": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch" + } + }, + "Spec.Distribution.CustomPatches.PatchesStrategicMerge": { + "type": "array", + "items": { + "type": "string" + } + }, + "Spec.Distribution.CustomPatches.Patch": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch.Target" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowNameChange": { + "type": "boolean" + }, + "allowKindChange": { + "type": "boolean" + } + } + }, + "path": { + "type": "string" + }, + "patch": { + "type": "string" + } + }, + "oneOf": [ + { + "required": ["path"] + }, + { + "required": ["patch"] + } + ] + }, + "Spec.Distribution.CustomPatches.Patch.Target": { + "type": "object", + "additionalProperties": false, + "properties": { + "group": { + "type": "string" + }, + "version": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "annotationSelector": { + "type": "string" + } + } + }, "Spec.ToolsConfiguration": { "type": "object", "additionalProperties": false, @@ -179,11 +324,10 @@ "additionalProperties": false, "properties": { "bucketName": { - "type": "string" + "$ref": "#/$defs/Types.AwsS3BucketName" }, "keyPrefix": { - "type": "string", - "maxLength": 37 + "$ref": "#/$defs/Types.AwsS3KeyPrefix" }, "region": { "$ref": "#/$defs/Types.AwsRegion" @@ -202,8 +346,81 @@ "properties": { "vpc": { "$ref": "#/$defs/Spec.Infrastructure.Vpc" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpn" } - } + }, + "allOf": [ + { + "if": { + "allOf": [ + { + "properties": { + "vpc": { + "type": "null" + } + } + }, + { + "not": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "required": ["vpcId"] + } + } + } + }, + { + "if": { + "allOf": [ + { + "not": { + "properties": { + "vpc": { + "type": "null" + } + } + } + }, + { + "not": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] }, "Spec.Infrastructure.Vpc": { "type": "object", @@ -211,9 +428,6 @@ "properties": { "network": { "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" - }, - "vpn": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" } }, "required": [ @@ -258,7 +472,7 @@ "public" ] }, - "Spec.Infrastructure.Vpc.Vpn": { + "Spec.Infrastructure.Vpn": { "type": "object", "additionalProperties": false, "properties": { @@ -284,7 +498,10 @@ "$ref": "#/$defs/Types.Cidr" }, "ssh": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + "$ref": "#/$defs/Spec.Infrastructure.Vpn.Ssh" + }, + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" } }, "required": [ @@ -292,7 +509,7 @@ "vpnClientsSubnetCidr" ] }, - "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "Spec.Infrastructure.Vpn.Ssh": { "type": "object", "additionalProperties": false, "properties": { @@ -313,7 +530,8 @@ "type": "array", "items": { "type": "string" - } + }, + "minItems": 1 }, "allowedFromCidrs": { "type": "array", @@ -348,7 +566,7 @@ "nodeAllowedSshPublicKey": { "anyOf": [ { - "$ref": "#/$defs/Types.SshPubKey" + "$ref": "#/$defs/Types.AwsSshPubKey" }, { "$ref": "#/$defs/Types.FileRef" @@ -641,6 +859,9 @@ }, "modules": { "$ref": "#/$defs/Spec.Distribution.Modules" + }, + "customPatches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches" } }, "required": [ @@ -760,6 +981,9 @@ "monitoring": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" }, + "networking": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking" + }, "policy": { "$ref": "#/$defs/Spec.Distribution.Modules.Policy" } @@ -775,7 +999,7 @@ "additionalProperties": false, "properties": { "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides" }, "baseDomain": { "type": "string" @@ -789,8 +1013,8 @@ "dns": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" }, - "externalDns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + "forecastle": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Forecastle" } }, "required": [ @@ -799,6 +1023,24 @@ "nginx" ] }, + "Spec.Distribution.Modules.Ingress.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "ingresses": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides.Ingresses" + }, + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { "type": "object", "additionalProperties": false, @@ -808,6 +1050,15 @@ } } }, + "Spec.Distribution.Modules.Ingress.Forecastle": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Ingress.Nginx": { "type": "object", "additionalProperties": false, @@ -818,6 +1069,9 @@ }, "tls": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -876,6 +1130,9 @@ "properties": { "clusterIssuer": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -896,9 +1153,6 @@ "type": { "type": "string", "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" } }, "required": [ @@ -907,42 +1161,6 @@ "email" ] }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.ExternalDNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "privateIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "publicIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "privateIamRoleArn", - "publicIamRoleArn" - ] - }, "Spec.Distribution.Modules.Ingress.DNS": { "type": "object", "additionalProperties": false, @@ -952,6 +1170,9 @@ }, "private": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -984,29 +1205,54 @@ }, "create": { "type": "boolean" - }, - "vpcId": { - "type": "string" } }, "required": [ "name", - "create", - "vpcId" + "create" ] }, "Spec.Distribution.Modules.Logging": { "type": "object", "additionalProperties": false, "properties": { + "type": { + "type": "string", + "enum": ["opensearch", "loki"] + }, "overrides": { "$ref": "#/$defs/Types.FuryModuleOverrides" }, "opensearch": { "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + }, + "loki": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Loki" + }, + "cerebro": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Cerebro" + }, + "minio": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Minio" + }, + "operator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Operator" } }, - "required": ["opensearch"] + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "opensearch" + } + } + }, + "then": { + "required": ["opensearch"] + } + } + ] }, "Spec.Distribution.Modules.Logging.Opensearch": { "type": "object", @@ -1021,12 +1267,54 @@ }, "storageSize": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ "type" ] }, + "Spec.Distribution.Modules.Logging.Cerebro": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Minio": { + "type": "object", + "additionalProperties": false, + "properties": { + "storageSize": { + "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Loki": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Logging.Operator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Monitoring": { "type": "object", "additionalProperties": false, @@ -1039,6 +1327,18 @@ }, "alertmanager": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + }, + "grafana": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Grafana" + }, + "blackboxExporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.BlackboxExporter" + }, + "kubeStateMetrics": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.KubeStateMetrics" + }, + "x509Exporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.X509Exporter" } } }, @@ -1072,6 +1372,63 @@ } } }, + "Spec.Distribution.Modules.Monitoring.Grafana": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.BlackboxExporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.KubeStateMetrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.X509Exporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Networking": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + }, + "tigeraOperator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking.TigeraOperator" + } + } + }, + "Spec.Distribution.Modules.Networking.TigeraOperator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Policy": { "type": "object", "additionalProperties": false, @@ -1093,6 +1450,9 @@ "items": { "type": "string" } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } } }, @@ -1115,6 +1475,9 @@ "properties": { "eks": { "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["eks"] @@ -1127,16 +1490,13 @@ "$ref": "#/$defs/Types.AwsRegion" }, "bucketName": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "$ref": "#/$defs/Types.AwsS3BucketName", + "maxLength": 49 } }, "required": [ "region", - "bucketName", - "iamRoleArn" + "bucketName" ] }, "Spec.Distribution.Modules.Auth": { @@ -1174,11 +1534,7 @@ } }, "then": { - "properties": { - "auth": { - "required": ["dex", "pomerium", "baseDomain"] - } - } + "required": ["dex", "pomerium", "baseDomain"] }, "else": { "properties": { @@ -1300,6 +1656,9 @@ }, "policy": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -1333,6 +1692,9 @@ "properties": { "connectors": { "type": "array" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["connectors"] @@ -1341,52 +1703,48 @@ "type": "object", "additionalProperties": false, "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } }, "ebsCsiDriver": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } }, "loadBalancerController": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } + }, + "ebsSnapshotController": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" } } }, - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "iamRoleArn" - ] - }, - "Types.SemVer": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" @@ -1456,6 +1814,10 @@ "type": "string", "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" }, + "Types.AwsSshPubKey": { + "type": "string", + "pattern": "^ssh\\-(ed25519|rsa)\\s+" + }, "Types.AwsSubnetId": { "type": "string", "pattern": "^subnet\\-[0-9a-f]{17}$" @@ -1469,6 +1831,24 @@ "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" }, + "Types.AwsS3BucketName": { + "type": "string", + "allOf": [ + { + "pattern": "^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$" + }, + { + "not": { + "pattern": "^xn--|-s3alias$" + } + } + ] + }, + "Types.AwsS3KeyPrefix": { + "type": "string", + "pattern": "^[A-z0-9][A-z0-9!-_.*'()]+$", + "maxLength": 960 + }, "Types.KubeLabels": { "type": "object", "additionalProperties": { "type": "string" } @@ -1556,6 +1936,21 @@ } } }, + "Types.FuryModuleComponentOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Types.FuryModuleOverridesIngress": { "type": "object", "additionalProperties": false, diff --git a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl index 44998f975..e0d79632c 100644 --- a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl +++ b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl @@ -47,32 +47,32 @@ spec: - 10.1.48.0/24 - 10.1.49.0/24 - 10.1.50.0/24 - # This section defines the creation of VPN bastions - vpn: - # The number of instance to create, 0 to skip the creation - instances: 2 - # The port used by the OpenVPN server - port: 1194 - # The size of the AWS ec2 instance - instanceType: t3.micro - # The size of the disk in GB - diskSize: 50 - # The username of the account to create in the bastion's operating system - operatorName: sighup - # The dhParamsBits size used for the creation of the .pem file that will be used in the dh openvpn server.conf file - dhParamsBits: 2048 - # The CIDR that will be used to assign IP addresses to the VPN clients when connected - vpnClientsSubnetCidr: 172.16.0.0/16 - # ssh access settings - ssh: - # Not yet supported - publicKeys: [] - # The github user name list that will be used to get the ssh public key that will be added as authorized key to the operatorName user - githubUsersName: - - johndoe - # The CIDR enabled in the security group that can access the bastions in SSH - allowedFromCidrs: - - 0.0.0.0/0 + # This section defines the creation of VPN bastions + vpn: + # The number of instance to create, 0 to skip the creation + instances: 2 + # The port used by the OpenVPN server + port: 1194 + # The size of the AWS ec2 instance + instanceType: t3.micro + # The size of the disk in GB + diskSize: 50 + # The username of the account to create in the bastion's operating system + operatorName: sighup + # The dhParamsBits size used for the creation of the .pem file that will be used in the dh openvpn server.conf file + dhParamsBits: 2048 + # The CIDR that will be used to assign IP addresses to the VPN clients when connected + vpnClientsSubnetCidr: 172.16.0.0/16 + # ssh access settings + ssh: + # Not yet supported + publicKeys: [] + # The github user name list that will be used to get the ssh public key that will be added as authorized key to the operatorName user + githubUsersName: + - johndoe + # The CIDR enabled in the security group that can access the bastions in SSH + allowedFromCidrs: + - 0.0.0.0/0 # This section describes how the EKS cluster will be created kubernetes: # This key contains the ssh public key that can connect to the nodes via SSH using the ec2-user user diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index 01eacf22e..9a37dc3cb 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -197,7 +197,6 @@ spec: velero: eks: bucketName: example-velero - iamRoleArn: arn:aws:iam::123456789012:role/example-velero # private. region: eu-west-1 auth: provider: From 028f5ab9281220b8405730cee3c5baed7561ff1c Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 10:48:44 +0200 Subject: [PATCH 190/383] fix: e2e tests pt2 --- test/data/e2e/create/cluster/data/furyctl.yaml | 1 - test/data/e2e/validate/config/correct/furyctl.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/test/data/e2e/create/cluster/data/furyctl.yaml b/test/data/e2e/create/cluster/data/furyctl.yaml index 9a37dc3cb..e6e34e3e0 100644 --- a/test/data/e2e/create/cluster/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/data/furyctl.yaml @@ -129,7 +129,6 @@ spec: private: create: true name: "internal.fury-demo.sighup.io" - vpcId: "vpc123123123123" logging: overrides: nodeSelector: {} diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index 9a37dc3cb..e6e34e3e0 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -129,7 +129,6 @@ spec: private: create: true name: "internal.fury-demo.sighup.io" - vpcId: "vpc123123123123" logging: overrides: nodeSelector: {} From 8a0cf6b7de2713846bddfc639c02edef07755b3f Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 6 Apr 2023 17:18:35 +0200 Subject: [PATCH 191/383] chore(deps): bump fury-distribution version --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index c9fa75e5e..e2d4af051 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 + github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index 4e97ec85b..6f4cc5164 100644 --- a/go.sum +++ b/go.sum @@ -298,10 +298,8 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= -github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:TjnYLyTTEU4Vr+WQbt2kuE5snc819hLw4YTgIqBB8PQ= -github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 h1:s6lZw+WzoqkAfOOKMeWaCH2P2y/vi7t7jG5FvG0crV8= +github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 324832c731517514234546506277c6a174295c53 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 12 Apr 2023 11:56:31 +0200 Subject: [PATCH 192/383] chore(aws): improve log message --- internal/apis/kfd/v1alpha2/eks/creator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 14d71d608..740e597c0 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -397,10 +397,10 @@ func (v *ClusterCreator) storeClusterConfig() error { Kubeconfig: v.paths.Kubeconfig, }, true, true, false) - logrus.Info("Applying secret...") + logrus.Info("Saving furyctl configuration file in the cluster...") if err := runner.Apply(secretPath); err != nil { - return fmt.Errorf("error while applying secret: %w", err) + return fmt.Errorf("error while saving furyctl configuration file in the cluster: %w", err) } return nil From 4a412b69f5756fd08504578c323a178ac95c11ea Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 27 Mar 2023 11:27:41 +0200 Subject: [PATCH 193/383] feat(eks): implement support for public clusters --- .../provisioners/bootstrap/aws/main.tf.tpl | 51 +++++++---- configs/provisioners/bootstrap/aws/output.tf | 41 ++++----- .../provisioners/bootstrap/aws/variables.tf | 46 ++++++++-- configs/provisioners/cluster/eks/main.tf.tpl | 31 ++++--- configs/provisioners/cluster/eks/variables.tf | 37 +++++--- go.mod | 2 + .../kfd/v1alpha2/eks/create/infrastructure.go | 56 +++++++----- .../kfd/v1alpha2/eks/create/kubernetes.go | 90 +++++++++++-------- internal/apis/kfd/v1alpha2/eks/creator.go | 15 +--- internal/apis/kfd/v1alpha2/eks/deleter.go | 15 +--- 10 files changed, 224 insertions(+), 160 deletions(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index 528c15cdb..df64b405d 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -29,21 +29,38 @@ provider "aws" { } } -module "vpc-and-vpn" { - source = "{{ .kubernetes.installerPath }}" - - name = var.name - network_cidr = var.network_cidr - public_subnetwork_cidrs = var.public_subnetwork_cidrs - private_subnetwork_cidrs = var.private_subnetwork_cidrs - vpn_subnetwork_cidr = var.vpn_subnetwork_cidr - vpn_port = var.vpn_port - vpn_instances = var.vpn_instances - vpn_instance_type = var.vpn_instance_type - vpn_instance_disk_size = var.vpn_instance_disk_size - vpn_operator_name = var.vpn_operator_name - vpn_dhparams_bits = var.vpn_dhparams_bits - vpn_operator_cidrs = var.vpn_operator_cidrs - vpn_ssh_users = var.vpn_ssh_users - tags = var.tags +module "vpc" { + source = "{{ .kubernetes.vpcInstallerPath }}" + + count = var.vpc_enabled ? 1 : 0 + + name = var.name + network_cidr = var.network_cidr + tags = var.tags + + private_subnetwork_cidrs = var.vpc_private_subnetwork_cidrs + public_subnetwork_cidrs = var.vpc_public_subnetwork_cidrs +} + +module "vpn" { + source = "{{ .kubernetes.vpnInstallerPath }}" + + count = var.vpn_enabled ? 1 : 0 + + name = var.name + network_cidr = var.network_cidr + tags = var.tags + + vpc_id = var.vpc_enabled ? one(module.vpc[*].vpc_id) : var.vpn_vpc_id + public_subnets = var.vpc_enabled ? one(module.vpc[*].public_subnets) : var.vpn_public_subnets + + vpn_subnetwork_cidr = var.vpn_subnetwork_cidr + vpn_port = var.vpn_port + vpn_instances = var.vpn_instances + vpn_instance_type = var.vpn_instance_type + vpn_instance_disk_size = var.vpn_instance_disk_size + vpn_operator_name = var.vpn_operator_name + vpn_dhparams_bits = var.vpn_dhparams_bits + vpn_operator_cidrs = var.vpn_operator_cidrs + vpn_ssh_users = var.vpn_ssh_users } diff --git a/configs/provisioners/bootstrap/aws/output.tf b/configs/provisioners/bootstrap/aws/output.tf index 2a7f45994..62046ad25 100644 --- a/configs/provisioners/bootstrap/aws/output.tf +++ b/configs/provisioners/bootstrap/aws/output.tf @@ -1,51 +1,46 @@ /** - * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. + * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ -output "furyagent" { - description = "furyagent.yml used by the vpn instance and ready to use to create a vpn profile" - sensitive = true - value = module.vpc-and-vpn.furyagent -} - -output "vpn_ip" { - description = "VPN instance IPs" - value = module.vpc-and-vpn.vpn_ip -} - -output "vpn_operator_name" { - description = "SSH Username to log into the VPN Instance" - value = var.vpn_operator_name -} - output "vpc_id" { description = "The ID of the VPC" - value = module.vpc-and-vpn.vpc_id + value = one(module.vpc[*].vpc_id) } output "vpc_cidr_block" { description = "The CIDR block of the VPC" - value = module.vpc-and-vpn.vpc_cidr_block + value = one(module.vpc[*].vpc_cidr_block) } output "public_subnets" { description = "List of IDs of public subnets" - value = module.vpc-and-vpn.public_subnets + value = one(module.vpc[*].public_subnets) } output "public_subnets_cidr_blocks" { description = "List of cidr_blocks of public subnets" - value = module.vpc-and-vpn.public_subnets_cidr_blocks + value = one(module.vpc[*].public_subnets_cidr_blocks) } output "private_subnets" { description = "List of IDs of private subnets" - value = module.vpc-and-vpn.private_subnets + value = one(module.vpc[*].private_subnets) } output "private_subnets_cidr_blocks" { description = "List of cidr_blocks of private subnets" - value = module.vpc-and-vpn.private_subnets_cidr_blocks + value = one(module.vpc[*].private_subnets_cidr_blocks) +} + +output "furyagent" { + description = "furyagent.yml used by the vpn instance and ready to use to create a vpn profile" + sensitive = true + value = var.vpn_enabled ? one(module.vpn[*].furyagent) : null +} + +output "vpn_ip" { + description = "VPN instance IPs" + value = var.vpn_enabled ? one(module.vpn[*].vpn_ip) : null } diff --git a/configs/provisioners/bootstrap/aws/variables.tf b/configs/provisioners/bootstrap/aws/variables.tf index 1f04ecc4a..2a0f49bb6 100644 --- a/configs/provisioners/bootstrap/aws/variables.tf +++ b/configs/provisioners/bootstrap/aws/variables.tf @@ -14,25 +14,52 @@ variable "network_cidr" { type = string } -variable "public_subnetwork_cidrs" { +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "vpc_enabled" { + description = "Enable VPC creation" + type = bool + default = true +} + +variable "vpc_public_subnetwork_cidrs" { description = "Public subnet CIDRs" type = list(string) + default = [] } -variable "private_subnetwork_cidrs" { +variable "vpc_private_subnetwork_cidrs" { description = "Private subnet CIDRs" type = list(string) + default = [] } -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} +variable "vpn_enabled" { + description = "Enable VPN" + type = bool + default = true +} + +variable "vpn_vpc_id" { + description = "ID of the VPC" + type = string + default = "" +} + +variable "vpn_public_subnets" { + description = "Enable VPC" + type = list(string) + default = [] } variable "vpn_subnetwork_cidr" { - description = "VPN Subnet CIDR, should be different from the network_cidr" + description = "CIDR used to assign VPN clients IP addresses, should be different from the network_cidr" type = string + default = "192.168.200.0/24" } variable "vpn_instances" { @@ -66,7 +93,7 @@ variable "vpn_operator_name" { } variable "vpn_dhparams_bits" { - description = "Diffie–Hellman (D-H) key size in bytes" + description = "Diffie-Hellman (D-H) key size in bytes" type = number default = 2048 } @@ -78,6 +105,7 @@ variable "vpn_operator_cidrs" { } variable "vpn_ssh_users" { - description = "GitHub users id to sync public rsa keys. Example angelbarrera92" + description = "GitHub users to sync public keys for SSH access" type = list(string) + default = [] } diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index 5dfa5b7cb..05b682265 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -39,25 +39,28 @@ provider "aws" { } provider "kubernetes" { - host = data.aws_eks_cluster.fury.endpoint - cluster_ca_certificate = base64decode(data.aws_eks_cluster.fury.certificate_authority[0].data) - token = data.aws_eks_cluster_auth.fury.token - load_config_file = false + host = data.aws_eks_cluster.fury.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.fury.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.fury.token + load_config_file = false } module "fury" { source = "{{ .kubernetes.installerPath }}" - cluster_name = var.cluster_name - cluster_version = var.cluster_version - cluster_log_retention_days = var.cluster_log_retention_days - network = var.network - subnetworks = var.subnetworks - dmz_cidr_range = var.dmz_cidr_range - ssh_public_key = var.ssh_public_key - node_pools = var.node_pools - node_pools_launch_kind = var.node_pools_launch_kind - tags = var.tags + cluster_name = var.cluster_name + cluster_version = var.cluster_version + cluster_log_retention_days = var.cluster_log_retention_days + cluster_endpoint_public_access = var.cluster_endpoint_public_access + cluster_endpoint_public_access_cidrs = var.cluster_endpoint_public_access_cidrs + cluster_endpoint_private_access = var.cluster_endpoint_private_access + cluster_endpoint_private_access_cidrs = var.cluster_endpoint_private_access_cidrs + network = var.network + subnetworks = var.subnetworks + ssh_public_key = var.ssh_public_key + node_pools = var.node_pools + node_pools_launch_kind = var.node_pools_launch_kind + tags = var.tags # Specific AWS variables. # Enables managing auth using these variables diff --git a/configs/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf index a1246be10..573f35e39 100644 --- a/configs/provisioners/cluster/eks/variables.tf +++ b/configs/provisioners/cluster/eks/variables.tf @@ -11,13 +11,34 @@ variable "cluster_name" { variable "cluster_version" { type = string - description = "Kubernetes Cluster Version. Look at the cloud providers documentation to discover available versions. EKS example -> 1.24" + description = "Kubernetes Cluster Version. Look at the cloud providers documentation to discover available versions." } variable "cluster_log_retention_days" { - type = number + type = number default = 90 } + +variable "cluster_endpoint_private_access" { + type = bool + default = false +} + +variable "cluster_endpoint_private_access_cidrs" { + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "cluster_endpoint_public_access" { + type = bool + default = false +} + +variable "cluster_endpoint_public_access_cidrs" { + type = list(string) + default = ["0.0.0.0/0"] +} + variable "network" { type = string description = "Network where the Kubernetes cluster will be hosted" @@ -28,10 +49,6 @@ variable "subnetworks" { description = "List of subnets where the cluster will be hosted" } -variable "dmz_cidr_range" { - description = "Network CIDR range from where cluster control plane will be accessible" -} - variable "ssh_public_key" { type = string description = "Cluster administrator public ssh key. Used to access cluster nodes with the operator_ssh_user" @@ -68,7 +85,7 @@ variable "node_pools" { } variable "node_pools_launch_kind" { - type = string + type = string description = "Choose if the node pools will use launch_configurations, launch_templates or both" } @@ -78,12 +95,6 @@ variable "tags" { default = {} } -variable "resource_group_name" { - type = string - description = "Resource group name where every resource will be placed. Required only in AKS installer (*)" - default = "" -} - variable "eks_map_accounts" { description = "Additional AWS account numbers to add to the aws-auth configmap" type = list(string) diff --git a/go.mod b/go.mod index e2d4af051..a9b53c327 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/sighupio/furyctl go 1.19 +replace "github.com/sighupio/fury-distribution" => "../../distribution" + require ( github.com/Al-Pragliola/go-version v1.6.2 github.com/Masterminds/sprig/v3 v3.2.3 diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index fa368a95d..ee3afb2f8 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -152,7 +152,8 @@ func (i *Infrastructure) copyFromTemplate() error { targetTfDir := path.Join(i.Path, "terraform") prefix := "infra" - eksInstallerPath := path.Join(i.Path, "..", "vendor", "installers", "eks", "modules", "vpc-and-vpn") + vpcInstallerPath := path.Join(i.Path, "..", "vendor", "installers", "eks", "modules", "vpc") + vpnInstallerPath := path.Join(i.Path, "..", "vendor", "installers", "eks", "modules", "vpn") cfg.Data = map[string]map[any]any{ "spec": { @@ -160,7 +161,8 @@ func (i *Infrastructure) copyFromTemplate() error { "tags": i.furyctlConf.Spec.Tags, }, "kubernetes": { - "installerPath": eksInstallerPath, + "vpcInstallerPath": vpcInstallerPath, + "vpnInstallerPath": vpnInstallerPath, }, "terraform": { "backend": map[string]any{ @@ -215,19 +217,24 @@ func (i *Infrastructure) createTfVars() error { privateSubnetworkCidrs[i] = fmt.Sprintf("\"%v\"", cidr) } - err = bytesx.SafeWriteToBuffer(&buffer, - "public_subnetwork_cidrs = [%v]\n", + if err := bytesx.SafeWriteToBuffer(&buffer, + "vpc_enabled = %v\n", + i.furyctlConf.Spec.Infrastructure.Vpc != nil, + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if err = bytesx.SafeWriteToBuffer(&buffer, + "vpc_public_subnetwork_cidrs = [%v]\n", strings.Join(publicSubnetworkCidrs, ","), - ) - if err != nil { + ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - err = bytesx.SafeWriteToBuffer(&buffer, - "private_subnetwork_cidrs = [%v]\n", + if err := bytesx.SafeWriteToBuffer(&buffer, + "vpc_private_subnetwork_cidrs = [%v]\n", strings.Join(privateSubnetworkCidrs, ","), - ) - if err != nil { + ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -242,17 +249,24 @@ func (i *Infrastructure) createTfVars() error { } func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { - err := bytesx.SafeWriteToBuffer( + vpnEnabled := (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil) && + (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances != nil) && + (*i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances > 0) + + if err := bytesx.SafeWriteToBuffer(buffer, "vpn_enabled = %v\n", vpnEnabled); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if err := bytesx.SafeWriteToBuffer( buffer, "vpn_subnetwork_cidr = \"%v\"\n", i.furyctlConf.Spec.Infrastructure.Vpn.VpnClientsSubnetCidr, - ) - if err != nil { + ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } if i.furyctlConf.Spec.Infrastructure.Vpn.Instances != nil { - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_instances = %v\n", *i.furyctlConf.Spec.Infrastructure.Vpn.Instances, @@ -263,7 +277,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { } if i.furyctlConf.Spec.Infrastructure.Vpn.Port != nil && *i.furyctlConf.Spec.Infrastructure.Vpn.Port != 0 { - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_port = %v\n", *i.furyctlConf.Spec.Infrastructure.Vpn.Port, @@ -275,7 +289,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpn.InstanceType != nil && *i.furyctlConf.Spec.Infrastructure.Vpn.InstanceType != "" { - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_instance_type = \"%v\"\n", *i.furyctlConf.Spec.Infrastructure.Vpn.InstanceType, @@ -287,7 +301,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpn.DiskSize != nil && *i.furyctlConf.Spec.Infrastructure.Vpn.DiskSize != 0 { - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_instance_disk_size = %v\n", *i.furyctlConf.Spec.Infrastructure.Vpn.DiskSize, @@ -299,7 +313,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpn.OperatorName != nil && *i.furyctlConf.Spec.Infrastructure.Vpn.OperatorName != "" { - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_operator_name = \"%v\"\n", *i.furyctlConf.Spec.Infrastructure.Vpn.OperatorName, @@ -311,7 +325,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { if i.furyctlConf.Spec.Infrastructure.Vpn.DhParamsBits != nil && *i.furyctlConf.Spec.Infrastructure.Vpn.DhParamsBits != 0 { - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_dhparams_bits = %v\n", *i.furyctlConf.Spec.Infrastructure.Vpn.DhParamsBits, @@ -328,7 +342,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { allowedCidrs[i] = fmt.Sprintf("\"%v\"", cidr) } - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_operator_cidrs = [%v]\n", strings.Join(allowedCidrs, ","), @@ -345,7 +359,7 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { githubUsers[i] = fmt.Sprintf("\"%v\"", gu) } - err = bytesx.SafeWriteToBuffer( + err := bytesx.SafeWriteToBuffer( buffer, "vpn_ssh_users = [%v]\n", strings.Join(githubUsers, ","), diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index a2a872185..1a3c6b6a5 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -344,14 +344,11 @@ func (k *Kubernetes) mergeConfig() (template.Config, error) { func (k *Kubernetes) createTfVars() error { var buffer bytes.Buffer - var allowedCidrsSource []private.TypesCidr - subnetIdsSource := k.furyctlConf.Spec.Kubernetes.SubnetIds vpcIDSource := k.furyctlConf.Spec.Kubernetes.VpcId - if k.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess != nil { - allowedCidrsSource = k.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.AllowedCidrs - } + allowedClusterEndpointPrivateAccessCIDRs := k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccessCidrs + allowedClusterEndpointPublicAccessCIDRs := k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccessCidrs if infraOutJSON, err := os.ReadFile(path.Join(k.infraOutputsPath, "output.json")); err == nil { var infraOut terraform.OutputJSON @@ -398,7 +395,7 @@ func (k *Kubernetes) createTfVars() error { subnetIdsSource = subs vpcID := private.TypesAwsVpcId(v) vpcIDSource = &vpcID - allowedCidrsSource = []private.TypesCidr{private.TypesCidr(c)} + allowedClusterEndpointPrivateAccessCIDRs = append(allowedClusterEndpointPrivateAccessCIDRs, private.TypesCidr(c)) } } @@ -429,6 +426,54 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_endpoint_private_access = %v\n", + k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + clusterEndpointPrivateAccessCidrs := make([]string, len(allowedClusterEndpointPrivateAccessCIDRs)) + + for i, cidr := range allowedClusterEndpointPrivateAccessCIDRs { + clusterEndpointPrivateAccessCidrs[i] = fmt.Sprintf("\"%v\"", cidr) + } + + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_endpoint_private_access_cidrs = [%v]\n", + strings.Join(clusterEndpointPrivateAccessCidrs, ","), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_endpoint_public_access = %v\n", + k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + clusterEndpointPublicAccessCidrs := make([]string, len(allowedClusterEndpointPublicAccessCIDRs)) + + for i, cidr := range allowedClusterEndpointPublicAccessCIDRs { + clusterEndpointPublicAccessCidrs[i] = fmt.Sprintf("\"%v\"", cidr) + } + + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_endpoint_public_access_cidrs = [%v]\n", + strings.Join(clusterEndpointPublicAccessCidrs, ","), + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + err = bytesx.SafeWriteToBuffer( &buffer, "node_pools_launch_kind = \"%v\"\n", @@ -481,21 +526,6 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - dmzCidrRange := make([]string, len(allowedCidrsSource)) - - for i, cidr := range allowedCidrsSource { - dmzCidrRange[i] = fmt.Sprintf("\"%v\"", cidr) - } - - err = bytesx.SafeWriteToBuffer( - &buffer, - "dmz_cidr_range = [%v]\n", - strings.Join(dmzCidrRange, ","), - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - err = bytesx.SafeWriteToBuffer( &buffer, "ssh_public_key = \"%v\"\n", @@ -505,24 +535,6 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - if k.furyctlConf.Spec.Tags != nil && len(k.furyctlConf.Spec.Tags) > 0 { - var tags []byte - - tags, err := json.Marshal(k.furyctlConf.Spec.Tags) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - err = bytesx.SafeWriteToBuffer( - &buffer, - "tags = %v\n", - string(tags), - ) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - err = k.addAwsAuthToTfVars(&buffer) if err != nil { return fmt.Errorf("error writing AWS Auth to Terraform vars file: %w", err) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 740e597c0..4272524fd 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -156,10 +156,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: - if (v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || - v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && - !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -195,10 +192,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseDistribution: - if (v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || - v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && - !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -261,10 +255,7 @@ func (v *ClusterCreator) allPhases( } } - if (v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || - v.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && - !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !v.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 8c6d37ca7..52d33c921 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -120,10 +120,7 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseKubernetes: - if (d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || - d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && - !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -141,10 +138,7 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseDistribution: - if (d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || - d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && - !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -164,10 +158,7 @@ func (d *ClusterDeleter) Delete() error { "Sometimes this is not possible, for better results limit the scope with the --phase flag.") } - if (d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess == nil || - d.furyctlConf.Spec.Kubernetes.ApiServerEndpointAccess.Type == - private.SpecKubernetesAPIServerEndpointAccessTypePrivate) && - !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !d.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 1727f485eb709153592078ed47209c7d16e3d2df Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 3 Apr 2023 16:21:32 +0200 Subject: [PATCH 194/383] fix(eks): add guard for vpn instances --- internal/apis/kfd/v1alpha2/eks/create/infrastructure.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index ee3afb2f8..6c58528c9 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -250,8 +250,9 @@ func (i *Infrastructure) createTfVars() error { func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { vpnEnabled := (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil) && + (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances == nil) || (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances != nil) && - (*i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances > 0) + (*i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances > 0) if err := bytesx.SafeWriteToBuffer(buffer, "vpn_enabled = %v\n", vpnEnabled); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) From 692e0b640f1f7220ce3a7941297b8394ebcb14eb Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 3 Apr 2023 17:53:09 +0200 Subject: [PATCH 195/383] chore(golang): bump deps --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a9b53c327..3436fb2a3 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/sighupio/furyctl go 1.19 -replace "github.com/sighupio/fury-distribution" => "../../distribution" +replace github.com/sighupio/fury-distribution => ../../distribution require ( github.com/Al-Pragliola/go-version v1.6.2 From 7e306a0f92659a9919b76a9dcfdb304e234cd251 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 30 Mar 2023 20:01:29 +0200 Subject: [PATCH 196/383] feat: removed sleep, improved code --- .../kfd/v1alpha2/eks/delete/distribution.go | 212 +++++++++++------- internal/kubernetes/client.go | 150 +++++++++++++ internal/tool/awscli/runner.go | 19 ++ internal/tool/kubectl/runner.go | 8 +- internal/x/slices/disjoint.go | 51 +++++ internal/x/slices/disjoint_test.go | 104 +++++++++ 6 files changed, 458 insertions(+), 86 deletions(-) create mode 100644 internal/kubernetes/client.go create mode 100644 internal/x/slices/disjoint.go create mode 100644 internal/x/slices/disjoint_test.go diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 78a95a3eb..b29debfa9 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -11,17 +11,20 @@ import ( "path" "path/filepath" "regexp" + "strings" "time" "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/furyctl/internal/cluster" - "github.com/sighupio/furyctl/internal/tool/kubectl" + "github.com/sighupio/furyctl/internal/kubernetes" + "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" + "github.com/sighupio/furyctl/internal/x/slices" ) const ( @@ -36,11 +39,17 @@ var ( errClusterConnect = errors.New("error connecting to cluster") ) +type Ingress struct { + Name string + Host []string +} + type Distribution struct { *cluster.OperationPhase tfRunner *terraform.Runner kzRunner *kustomize.Runner - kubeRunner *kubectl.Runner + awsRunner *awscli.Runner + kubeClient *kubernetes.Client dryRun bool } @@ -77,13 +86,17 @@ func NewDistribution( WorkDir: path.Join(phase.Path, "manifests"), }, ), - kubeRunner: kubectl.NewRunner( + awsRunner: awscli.NewRunner( execx.NewStdExecutor(), - kubectl.Paths{ - Kubectl: phase.KubectlPath, - WorkDir: path.Join(phase.Path, "manifests"), - Kubeconfig: kubeconfig, + awscli.Paths{ + Awscli: "aws", + WorkDir: phase.Path, }, + ), + kubeClient: kubernetes.NewClient( + phase.KubectlPath, + path.Join(phase.Path, "manifests"), + kubeconfig, true, true, false, @@ -108,7 +121,7 @@ func (d *Distribution) Exec() error { logrus.Info("Checking cluster connectivity...") - if _, err := d.kubeRunner.Version(); err != nil { + if _, err := d.kubeClient.ToolVersion(); err != nil { return errClusterConnect } @@ -128,44 +141,44 @@ func (d *Distribution) Exec() error { return err } - err = d.kubeRunner.Delete(manifestsOutPath, "--dry-run=client") + _, err = d.kubeClient.DeleteFromPath(manifestsOutPath, "--dry-run=client") if err != nil { logrus.Errorf("error while deleting resources: %v", err) } logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") - err = d.getListOfResourcesNs("all", "ingress") + err = d.kubeClient.GetListOfResourcesNs("all", "ingress") if err != nil { logrus.Errorf("error while getting list of ingress resources: %v", err) } - err = d.getListOfResourcesNs("monitoring", "prometheus") + err = d.kubeClient.GetListOfResourcesNs("monitoring", "prometheus") if err != nil { logrus.Errorf("error while getting list of prometheus resources: %v", err) } - err = d.getListOfResourcesNs("monitoring", "persistentvolumeclaim") + err = d.kubeClient.GetListOfResourcesNs("monitoring", "persistentvolumeclaim") if err != nil { logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) } - err = d.getListOfResourcesNs("logging", "persistentvolumeclaim") + err = d.kubeClient.GetListOfResourcesNs("logging", "persistentvolumeclaim") if err != nil { logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) } - err = d.getListOfResourcesNs("logging", "statefulset") + err = d.kubeClient.GetListOfResourcesNs("logging", "statefulset") if err != nil { logrus.Errorf("error while getting list of statefulset resources: %v", err) } - err = d.getListOfResourcesNs("logging", "logging") + err = d.kubeClient.GetListOfResourcesNs("logging", "logging") if err != nil { logrus.Errorf("error while getting list of logging resources: %v", err) } - err = d.getListOfResourcesNs("ingress-nginx", "service") + err = d.kubeClient.GetListOfResourcesNs("ingress-nginx", "service") if err != nil { logrus.Errorf("error while getting list of service resources: %v", err) } @@ -173,12 +186,29 @@ func (d *Distribution) Exec() error { return nil } + ingressHosts, err := d.getIngressHosts() + if err != nil { + return err + } + + hostedZones, err := d.getHostedZones() + if err != nil { + return err + } + logrus.Info("Deleting ingresses...") if err = d.deleteIngresses(); err != nil { return err } + logrus.Info("Waiting for DNS records to be deleted...") + + err = d.checkPendingDNSRecords(ingressHosts, hostedZones) + if err != nil { + return err + } + logrus.Info("Deleting blocking resources...[PersistentVolumeClaims, StatefulSets, Logging, Prometheus]") if err = d.deleteBlockingResources(); err != nil { @@ -194,7 +224,7 @@ func (d *Distribution) Exec() error { logrus.Info("Deleting manifests...") - err = d.kubeRunner.Delete(manifestsOutPath) + _, err = d.kubeClient.DeleteFromPath(manifestsOutPath) if err != nil { logrus.Errorf("error while deleting resources: %v", err) } @@ -241,6 +271,10 @@ func (d *Distribution) buildManifests() (string, error) { func (d *Distribution) checkPendingResources() error { var errSvc, errPv, errIgrs error + var ingrs []kubernetes.Ingress + + var lbs, pvs []string + dur := time.Second * checkPendingResourcesDelay maxRetries := checkPendingResourcesMaxRetries @@ -251,11 +285,20 @@ func (d *Distribution) checkPendingResources() error { p := time.NewTicker(dur) if <-p.C; true { - errSvc = d.getLoadBalancers() + lbs, errSvc = d.kubeClient.GetLoadBalancers() + if errSvc == nil && len(lbs) > 0 { + errSvc = fmt.Errorf("%w: %v", errPendingResources, lbs) + } - errPv = d.getPersistentVolumes() + pvs, errPv = d.kubeClient.GetPersistentVolumes() + if errPv == nil && len(pvs) > 0 { + errPv = fmt.Errorf("%w: %v", errPendingResources, pvs) + } - errIgrs = d.getIngresses() + ingrs, errIgrs = d.kubeClient.GetIngresses() + if errIgrs == nil && len(ingrs) > 0 { + errIgrs = fmt.Errorf("%w: %v", errPendingResources, ingrs) + } if errSvc == nil && errPv == nil && errIgrs == nil { return nil @@ -271,49 +314,43 @@ func (d *Distribution) checkPendingResources() error { } func (d *Distribution) deleteIngresses() error { - dur := time.Minute * ingressAfterDeleteDelay - - _, err := d.kubeRunner.DeleteAllResources("ingress", "all") + _, err := d.kubeClient.DeleteAllResources("ingress", "all") if err != nil { return fmt.Errorf("error deleting ingresses: %w", err) } - logrus.Debugf("waiting for DNS records to be deleted...[ETA: 4 minutes]") - - time.Sleep(dur) - return nil } func (d *Distribution) deleteBlockingResources() error { dur := time.Minute * ingressAfterDeleteDelay - _, err := d.kubeRunner.DeleteAllResources("prometheus", "monitoring") + _, err := d.kubeClient.DeleteAllResources("prometheus", "monitoring") if err != nil { return fmt.Errorf("error deleting prometheus resources: %w", err) } - _, err = d.kubeRunner.DeleteAllResources("pvc", "monitoring") + _, err = d.kubeClient.DeleteAllResources("pvc", "monitoring") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'monitoring': %w", err) } - _, err = d.kubeRunner.DeleteAllResources("logging", "logging") + _, err = d.kubeClient.DeleteAllResources("logging", "logging") if err != nil { return fmt.Errorf("error deleting logging resources: %w", err) } - _, err = d.kubeRunner.DeleteAllResources("sts", "logging") + _, err = d.kubeClient.DeleteAllResources("sts", "logging") if err != nil { return fmt.Errorf("error deleting sts in namespace 'logging': %w", err) } - _, err = d.kubeRunner.DeleteAllResources("pvc", "logging") + _, err = d.kubeClient.DeleteAllResources("pvc", "logging") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'logging': %w", err) } - _, err = d.kubeRunner.DeleteAllResources("svc", "ingress-nginx") + _, err = d.kubeClient.DeleteAllResources("svc", "ingress-nginx") if err != nil { return fmt.Errorf("error deleting svc in namespace 'ingress-nginx': %w", err) } @@ -324,82 +361,93 @@ func (d *Distribution) deleteBlockingResources() error { return nil } -func (d *Distribution) getLoadBalancers() error { - log, err := d.kubeRunner.Get("all", "svc", "-o", - "jsonpath='{.items[?(@.spec.type==\"LoadBalancer\")].metadata.name}'") +func (d *Distribution) getIngressHosts() ([]string, error) { + ingrs, err := d.kubeClient.GetIngresses() if err != nil { - return fmt.Errorf("error while reading resources from cluster: %w", err) + return nil, fmt.Errorf("error getting ingresses: %w", err) } - reg := regexp.MustCompile(`'(.*?)'`) - - logStringIndex := reg.FindStringIndex(log) + var hosts []string - if len(logStringIndex) == 0 { - return fmt.Errorf("%w: error while parsing kubectl get response", errPendingResources) + for _, ingress := range ingrs { + hosts = append(hosts, ingress.Host...) } - logString := log[logStringIndex[0]:logStringIndex[1]] + hosts = slices.Uniq(hosts) - if logString != "''" { - return fmt.Errorf("%w: %s", errPendingResources, logString) - } - - return nil + return hosts, nil } -func (d *Distribution) getListOfResourcesNs(ns, resName string) error { - _, err := d.kubeRunner.Get(ns, resName, "-o", - "jsonpath={range .items[*]}{\""+resName+" \"}\"{.metadata.name}\"{\" deleted (dry run)\"}{\"\\n\"}{end}") +func (d *Distribution) getHostedZones() (map[string]string, error) { + zones := make(map[string]string) + + route53, err := d.awsRunner.Route53("list-hosted-zones", "--query", "HostedZones[*].[Id,Name]", + "--output", "text") if err != nil { - return fmt.Errorf("error while reading resources from cluster: %w", err) + return zones, fmt.Errorf("error getting hosted zones: %w", err) } - return nil -} + re := regexp.MustCompile(`/hostedzone/(\S+)\t(\S+)\.`) -func (d *Distribution) getIngresses() error { - log, err := d.kubeRunner.Get("all", "ingress", "-o", "jsonpath='{.items[*].metadata.name}'") - if err != nil { - return fmt.Errorf("error while reading resources from cluster: %w", err) + matches := re.FindAllStringSubmatch(route53, -1) + + for _, match := range matches { + zones[match[1]] = match[2] } - reg := regexp.MustCompile(`'(.*?)'`) + return zones, nil +} - logStringIndex := reg.FindStringIndex(log) +func (d *Distribution) checkPendingDNSRecords(hosts []string, hostedZones map[string]string) error { + queue := make([]string, 0, len(hostedZones)) - if len(logStringIndex) == 0 { - return fmt.Errorf("%w: error while parsing kubectl get response", errPendingResources) + for zone := range hostedZones { + queue = append(queue, zone) } - logString := log[logStringIndex[0]:logStringIndex[1]] - - if logString != "''" { - return fmt.Errorf("%w: %s", errPendingResources, logString) + trimSuffix := func(a string) string { + return strings.TrimSuffix(a, ".") } - return nil -} + dur := time.Second * checkPendingResourcesDelay -func (d *Distribution) getPersistentVolumes() error { - log, err := d.kubeRunner.Get("all", "pv", "-o", "jsonpath='{.items[*].metadata.name}'") - if err != nil { - return fmt.Errorf("error while reading resources from cluster: %w", err) - } + maxRetries := checkPendingResourcesMaxRetries * len(hosts) - reg := regexp.MustCompile(`'(.*?)'`) + retries := 0 - logStringIndex := reg.FindStringIndex(log) + for retries < maxRetries { + p := time.NewTicker(dur) - if len(logStringIndex) == 0 { - return fmt.Errorf("%w: error while parsing kubectl get response", errPendingResources) - } + if <-p.C; true { + for _, zone := range queue { + domains, err := d.awsRunner.Route53("list-resource-record-sets", "--hosted-zone-id", zone, + "--query", "ResourceRecordSets[*].Name", "--output", "text") + if err != nil { + return fmt.Errorf("error getting hosted zone records: %w", err) + } + + re := regexp.MustCompile(`(\S+)\.`) + matches := re.FindAllString(domains, -1) + + if slices.DisjointTransform( + hosts, + matches, + nil, + trimSuffix, + ) { + queue = queue[1:] + } + } - logString := log[logStringIndex[0]:logStringIndex[1]] + if len(queue) == 0 { + return nil + } + } + + retries++ - if logString != "''" { - return fmt.Errorf("%w: %s", errPendingResources, logString) + p.Stop() } - return nil + return fmt.Errorf("%w: hostedzones %v", errCheckPendingResources, queue) } diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go new file mode 100644 index 000000000..6b2093fa6 --- /dev/null +++ b/internal/kubernetes/client.go @@ -0,0 +1,150 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubernetes + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/sighupio/furyctl/internal/tool/kubectl" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +type Ingress struct { + Name string + Host []string +} + +type Client struct { + kubeRunner *kubectl.Runner +} + +func NewClient(binPath, workDir, kubeconfig string, serverSide, skipNotFound, clientVersion bool) *Client { + return &Client{ + kubeRunner: kubectl.NewRunner( + execx.NewStdExecutor(), + kubectl.Paths{ + Kubectl: binPath, + WorkDir: workDir, + Kubeconfig: kubeconfig, + }, + serverSide, + skipNotFound, + clientVersion, + ), + } +} + +func (c *Client) GetIngresses() ([]Ingress, error) { + var result []Ingress + + log, err := c.kubeRunner.Get("all", "ingress", "-o", + "jsonpath='[{range .items[*]}{\"{\"}\"Name\": \"{.metadata.name}\", "+ + "\"Host\": [{range .spec.rules[*]}\"{.host}\",{end}]{\"}\"},{end}]'") + if err != nil { + return result, fmt.Errorf("error while reading resources from cluster: %w", err) + } + + reg := regexp.MustCompile(`'(.*?)'`) + + logStringIndex := reg.FindStringIndex(log) + + if len(logStringIndex) == 0 { + return result, nil + } + + out := log[logStringIndex[0]+1 : logStringIndex[1]-1] + + out = strings.ReplaceAll(out, ",]", "]") + + err = json.Unmarshal([]byte(out), &result) + if err != nil { + return result, fmt.Errorf("error while unmarshaling json: %w", err) + } + + return result, nil +} + +func (c *Client) GetPersistentVolumes() ([]string, error) { + var result []string + + log, err := c.kubeRunner.Get("all", "pv", "-o", "jsonpath='{.items[*].metadata.name}'") + if err != nil { + return result, fmt.Errorf("error while reading resources from cluster: %w", err) + } + + reg := regexp.MustCompile(`'(.*?)'`) + + logStringIndex := reg.FindStringIndex(log) + + if len(logStringIndex) == 0 { + return result, nil + } + + result = strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ") + + return result, nil +} + +func (c *Client) GetListOfResourcesNs(ns, resName string) error { + _, err := c.kubeRunner.Get(ns, resName, "-o", + "jsonpath={range .items[*]}{\""+resName+" \"}\"{.metadata.name}\"{\" deleted (dry run)\"}{\"\\n\"}{end}") + if err != nil { + return fmt.Errorf("error while reading resources from cluster: %w", err) + } + + return nil +} + +func (c *Client) GetLoadBalancers() ([]string, error) { + var result []string + + log, err := c.kubeRunner.Get("all", "svc", "-o", + "jsonpath='{.items[?(@.spec.type==\"LoadBalancer\")].metadata.name}'") + if err != nil { + return result, fmt.Errorf("error while reading resources from cluster: %w", err) + } + + reg := regexp.MustCompile(`'(.*?)'`) + + logStringIndex := reg.FindStringIndex(log) + + if len(logStringIndex) == 0 { + return result, nil + } + + result = strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ") + + return result, nil +} + +func (c *Client) DeleteAllResources(res, ns string) (string, error) { + result, err := c.kubeRunner.DeleteAllResources(res, ns) + if err != nil { + return result, fmt.Errorf("error while deleting resources from cluster: %w", err) + } + + return result, nil +} + +func (c *Client) DeleteFromPath(path string, params ...string) (string, error) { + result, err := c.kubeRunner.Delete(path, params...) + if err != nil { + return result, fmt.Errorf("error while deleting resources from cluster: %w", err) + } + + return result, nil +} + +func (c *Client) ToolVersion() (string, error) { + version, err := c.kubeRunner.Version() + if err != nil { + return "", fmt.Errorf("error while getting tool version: %w", err) + } + + return version, nil +} diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index 999de5681..a1fa6a38a 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -66,6 +66,25 @@ func (r *Runner) S3Api(params ...string) (string, error) { return out, nil } +func (r *Runner) Route53(sub string, params ...string) (string, error) { + args := []string{"route53", sub} + + if len(params) > 0 { + args = append(args, params...) + } + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error running awscli ec2 %s: %w", sub, err) + } + + return out, nil +} + func (r *Runner) Version() (string, error) { out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ Args: []string{"--version"}, diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 0eee9dfcd..bda2ec500 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -124,7 +124,7 @@ func (r *Runner) DeleteAllResources(res, ns string) (string, error) { return out, nil } -func (r *Runner) Delete(manifestPath string, params ...string) error { +func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { args := []string{"delete"} if r.paths.Kubeconfig != "" { @@ -141,16 +141,16 @@ func (r *Runner) Delete(manifestPath string, params ...string) error { args = append(args, "-f", manifestPath) - _, err := execx.CombinedOutputWithTimeout(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + res, err := execx.CombinedOutputWithTimeout(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, }), kubectlDeleteTimeout) if err != nil { - return fmt.Errorf("error deleting resources: %w", err) + return res, fmt.Errorf("error deleting resources: %w", err) } - return nil + return res, nil } func (r *Runner) Version() (string, error) { diff --git a/internal/x/slices/disjoint.go b/internal/x/slices/disjoint.go new file mode 100644 index 000000000..0be7b37c5 --- /dev/null +++ b/internal/x/slices/disjoint.go @@ -0,0 +1,51 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +type TransformFunc[T comparable] func(T) T + +func identity[T comparable](v T) T { + return v +} + +func Disjoint[T comparable](a, b []T) bool { + unique := make(map[T]bool, len(a)) + + for _, v := range a { + unique[v] = true + } + + for _, w := range b { + if unique[w] { + return false + } + } + + return true +} + +func DisjointTransform[T comparable](a, b []T, transformA, transformB TransformFunc[T]) bool { + unique := make(map[T]bool, len(a)) + + if transformA == nil { + transformA = identity[T] + } + + if transformB == nil { + transformB = identity[T] + } + + for _, v := range a { + unique[transformA(v)] = true + } + + for _, w := range b { + if unique[transformB(w)] { + return false + } + } + + return true +} diff --git a/internal/x/slices/disjoint_test.go b/internal/x/slices/disjoint_test.go new file mode 100644 index 000000000..8cb90d8af --- /dev/null +++ b/internal/x/slices/disjoint_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices_test + +import ( + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/x/slices" +) + +func TestDisjoint(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a []string + b []string + want bool + }{ + { + name: "empty", + a: []string{}, + b: []string{}, + want: true, + }, + { + name: "one element", + a: []string{"a"}, + b: []string{"b"}, + want: true, + }, + { + name: "not disjoint", + a: []string{"a", "b", "c"}, + b: []string{"a", "d", "e"}, + want: false, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := slices.Disjoint(tt.a, tt.b); got != tt.want { + t.Errorf("Disjoint() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDisjointTransform(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a []string + b []string + transformA slices.TransformFunc[string] + transformB slices.TransformFunc[string] + want bool + }{ + { + name: "empty", + a: []string{}, + b: []string{}, + want: true, + }, + { + name: "one element", + a: []string{"a"}, + b: []string{"b"}, + want: true, + }, + { + name: "not disjoint", + a: []string{"a", "b", "c"}, + b: []string{"a", "d", "e"}, + want: false, + }, + { + name: "transform", + a: []string{"A", "B", "C"}, + b: []string{"a", "b", "c"}, + transformA: strings.ToLower, + transformB: strings.ToUpper, + want: true, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := slices.DisjointTransform(tt.a, tt.b, tt.transformA, tt.transformB); got != tt.want { + t.Errorf("DisjointTransform() = %v, want %v", got, tt.want) + } + }) + } +} From 65247017c87f5fdc656baddcd73e97972ab970e9 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 30 Mar 2023 20:10:17 +0200 Subject: [PATCH 197/383] fix: check for hosts length --- .../apis/kfd/v1alpha2/eks/delete/distribution.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index b29debfa9..3c34a8cf4 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -202,11 +202,13 @@ func (d *Distribution) Exec() error { return err } - logrus.Info("Waiting for DNS records to be deleted...") + if len(ingressHosts) > 0 { + logrus.Info("Waiting for DNS records to be deleted...") - err = d.checkPendingDNSRecords(ingressHosts, hostedZones) - if err != nil { - return err + err = d.checkPendingDNSRecords(ingressHosts, hostedZones) + if err != nil { + return err + } } logrus.Info("Deleting blocking resources...[PersistentVolumeClaims, StatefulSets, Logging, Prometheus]") @@ -399,6 +401,10 @@ func (d *Distribution) getHostedZones() (map[string]string, error) { } func (d *Distribution) checkPendingDNSRecords(hosts []string, hostedZones map[string]string) error { + if len(hosts) == 0 { + return nil + } + queue := make([]string, 0, len(hostedZones)) for zone := range hostedZones { From 9f73415b866725d46ec717e63e826d89462f6ad4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 31 Mar 2023 12:09:26 +0200 Subject: [PATCH 198/383] fix: bug on empty slices --- internal/kubernetes/client.go | 38 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 6b2093fa6..d6ed0ae5a 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -53,7 +53,7 @@ func (c *Client) GetIngresses() ([]Ingress, error) { logStringIndex := reg.FindStringIndex(log) - if len(logStringIndex) == 0 { + if logStringIndex == nil { return result, nil } @@ -70,24 +70,20 @@ func (c *Client) GetIngresses() ([]Ingress, error) { } func (c *Client) GetPersistentVolumes() ([]string, error) { - var result []string - log, err := c.kubeRunner.Get("all", "pv", "-o", "jsonpath='{.items[*].metadata.name}'") if err != nil { - return result, fmt.Errorf("error while reading resources from cluster: %w", err) + return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } reg := regexp.MustCompile(`'(.*?)'`) logStringIndex := reg.FindStringIndex(log) - if len(logStringIndex) == 0 { - return result, nil + if logStringIndex == nil { + return []string{}, nil } - result = strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ") - - return result, nil + return cleanStringsSlice(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } func (c *Client) GetListOfResourcesNs(ns, resName string) error { @@ -101,25 +97,21 @@ func (c *Client) GetListOfResourcesNs(ns, resName string) error { } func (c *Client) GetLoadBalancers() ([]string, error) { - var result []string - log, err := c.kubeRunner.Get("all", "svc", "-o", "jsonpath='{.items[?(@.spec.type==\"LoadBalancer\")].metadata.name}'") if err != nil { - return result, fmt.Errorf("error while reading resources from cluster: %w", err) + return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } reg := regexp.MustCompile(`'(.*?)'`) logStringIndex := reg.FindStringIndex(log) - if len(logStringIndex) == 0 { - return result, nil + if logStringIndex == nil { + return []string{}, nil } - result = strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ") - - return result, nil + return cleanStringsSlice(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } func (c *Client) DeleteAllResources(res, ns string) (string, error) { @@ -148,3 +140,15 @@ func (c *Client) ToolVersion() (string, error) { return version, nil } + +func cleanStringsSlice(slice []string) []string { + var result []string + + for _, v := range slice { + if v != "" { + result = append(result, v) + } + } + + return result +} From fc4cfe78a4656003b29c5efd8fc8450c09044f12 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 31 Mar 2023 12:15:28 +0200 Subject: [PATCH 199/383] fix: linting --- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 6 +++++- internal/apis/kfd/v1alpha2/eks/creator.go | 6 +++--- internal/apis/kfd/v1alpha2/eks/deleter.go | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 1a3c6b6a5..af0faf26c 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -395,7 +395,11 @@ func (k *Kubernetes) createTfVars() error { subnetIdsSource = subs vpcID := private.TypesAwsVpcId(v) vpcIDSource = &vpcID - allowedClusterEndpointPrivateAccessCIDRs = append(allowedClusterEndpointPrivateAccessCIDRs, private.TypesCidr(c)) + + allowedClusterEndpointPrivateAccessCIDRs = append( + allowedClusterEndpointPrivateAccessCIDRs, + private.TypesCidr(c), + ) } } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 4272524fd..5269291dc 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -156,7 +156,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -192,7 +192,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseDistribution: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -255,7 +255,7 @@ func (v *ClusterCreator) allPhases( } } - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 52d33c921..3c6316e46 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -120,7 +120,7 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseKubernetes: - if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -138,7 +138,7 @@ func (d *ClusterDeleter) Delete() error { return nil case cluster.OperationPhaseDistribution: - if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !d.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -158,7 +158,7 @@ func (d *ClusterDeleter) Delete() error { "Sometimes this is not possible, for better results limit the scope with the --phase flag.") } - if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess == true && !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !d.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 14f62724b4e02597a4fce6e04af3377dbd196070 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 31 Mar 2023 12:43:29 +0200 Subject: [PATCH 200/383] fix: e2e tests --- test/data/e2e/create/cluster/data/kfd.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/data/e2e/create/cluster/data/kfd.yaml b/test/data/e2e/create/cluster/data/kfd.yaml index 332313e8a..31e8e7f01 100644 --- a/test/data/e2e/create/cluster/data/kfd.yaml +++ b/test/data/e2e/create/cluster/data/kfd.yaml @@ -4,18 +4,18 @@ version: v1.24.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 + auth: v0.0.2 + aws: v2.1.0 + dr: v1.10.1 + ingress: feat/change-behaviour-on-externaldns-module + logging: v3.0.1 + monitoring: v2.0.0 + opa: v1.7.3 networking: v1.10.0 kubernetes: eks: version: 1.24 - installer: v1.10.0 + installer: feat/137-support-public-cluster furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -32,4 +32,4 @@ tools: version: 0.15.4 eks: awscli: - version: "*" + version: 2.8.12 \ No newline at end of file From cdc38eb4d4519792057b901b94d5a509b94a0864 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 31 Mar 2023 12:48:38 +0200 Subject: [PATCH 201/383] chore: added new line at EOF --- test/data/e2e/create/cluster/data/kfd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/data/e2e/create/cluster/data/kfd.yaml b/test/data/e2e/create/cluster/data/kfd.yaml index 31e8e7f01..46c44ad11 100644 --- a/test/data/e2e/create/cluster/data/kfd.yaml +++ b/test/data/e2e/create/cluster/data/kfd.yaml @@ -32,4 +32,4 @@ tools: version: 0.15.4 eks: awscli: - version: 2.8.12 \ No newline at end of file + version: 2.8.12 From be974922f8cdb0ce6c980612b8c86b7b2d9b84c2 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 3 Apr 2023 16:43:02 +0200 Subject: [PATCH 202/383] chore: improve code post review --- .../kfd/v1alpha2/eks/delete/distribution.go | 48 ++--- internal/kubernetes/client.go | 43 ++-- internal/kubernetes/client_test.go | 191 ++++++++++++++++++ internal/x/slices/clean.go | 20 ++ internal/x/slices/clean_test.go | 54 +++++ internal/x/slices/disjoint.go | 3 + 6 files changed, 304 insertions(+), 55 deletions(-) create mode 100644 internal/kubernetes/client_test.go create mode 100644 internal/x/slices/clean.go create mode 100644 internal/x/slices/clean_test.go diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 3c34a8cf4..c473b61d0 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -37,6 +37,8 @@ var ( errCheckPendingResources = errors.New("error while checking pending resources") errPendingResources = errors.New("pending resources: ") errClusterConnect = errors.New("error connecting to cluster") + hostedZoneRegex = regexp.MustCompile(`/hostedzone/(\S+)\t(\S+)\.`) + recordSetsRegex = regexp.MustCompile(`(\S+)\.`) ) type Ingress struct { @@ -100,6 +102,7 @@ func NewDistribution( true, true, false, + execx.NewStdExecutor(), ), dryRun: dryRun, }, nil @@ -110,8 +113,7 @@ func (d *Distribution) Exec() error { logrus.Debug("Delete: running distribution phase...") - err := iox.CheckDirIsEmpty(d.OperationPhase.Path) - if err == nil { + if err := iox.CheckDirIsEmpty(d.OperationPhase.Path); err == nil { logrus.Info("Kubernetes Fury Distribution already deleted, skipping...") logrus.Debug("Distribution phase already executed, skipping...") @@ -141,45 +143,37 @@ func (d *Distribution) Exec() error { return err } - _, err = d.kubeClient.DeleteFromPath(manifestsOutPath, "--dry-run=client") - if err != nil { + if _, err = d.kubeClient.DeleteFromPath(manifestsOutPath, "--dry-run=client"); err != nil { logrus.Errorf("error while deleting resources: %v", err) } logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") - err = d.kubeClient.GetListOfResourcesNs("all", "ingress") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("all", "ingress"); err != nil { logrus.Errorf("error while getting list of ingress resources: %v", err) } - err = d.kubeClient.GetListOfResourcesNs("monitoring", "prometheus") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("monitoring", "prometheus"); err != nil { logrus.Errorf("error while getting list of prometheus resources: %v", err) } - err = d.kubeClient.GetListOfResourcesNs("monitoring", "persistentvolumeclaim") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("monitoring", "persistentvolumeclaim"); err != nil { logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) } - err = d.kubeClient.GetListOfResourcesNs("logging", "persistentvolumeclaim") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("logging", "persistentvolumeclaim"); err != nil { logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) } - err = d.kubeClient.GetListOfResourcesNs("logging", "statefulset") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("logging", "statefulset"); err != nil { logrus.Errorf("error while getting list of statefulset resources: %v", err) } - err = d.kubeClient.GetListOfResourcesNs("logging", "logging") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("logging", "logging"); err != nil { logrus.Errorf("error while getting list of logging resources: %v", err) } - err = d.kubeClient.GetListOfResourcesNs("ingress-nginx", "service") - if err != nil { + if err = d.kubeClient.GetListOfResourcesNs("ingress-nginx", "service"); err != nil { logrus.Errorf("error while getting list of service resources: %v", err) } @@ -205,8 +199,7 @@ func (d *Distribution) Exec() error { if len(ingressHosts) > 0 { logrus.Info("Waiting for DNS records to be deleted...") - err = d.checkPendingDNSRecords(ingressHosts, hostedZones) - if err != nil { + if err = d.assertEmptyDNSRecords(ingressHosts, hostedZones); err != nil { return err } } @@ -233,15 +226,13 @@ func (d *Distribution) Exec() error { logrus.Info("Checking pending resources...") - err = d.checkPendingResources() - if err != nil { + if err = d.checkPendingResources(); err != nil { return err } logrus.Info("Deleting infra resources...") - err = d.tfRunner.Destroy() - if err != nil { + if err = d.tfRunner.Destroy(); err != nil { return fmt.Errorf("error while deleting infra resources: %w", err) } @@ -389,9 +380,7 @@ func (d *Distribution) getHostedZones() (map[string]string, error) { return zones, fmt.Errorf("error getting hosted zones: %w", err) } - re := regexp.MustCompile(`/hostedzone/(\S+)\t(\S+)\.`) - - matches := re.FindAllStringSubmatch(route53, -1) + matches := hostedZoneRegex.FindAllStringSubmatch(route53, -1) for _, match := range matches { zones[match[1]] = match[2] @@ -400,7 +389,7 @@ func (d *Distribution) getHostedZones() (map[string]string, error) { return zones, nil } -func (d *Distribution) checkPendingDNSRecords(hosts []string, hostedZones map[string]string) error { +func (d *Distribution) assertEmptyDNSRecords(hosts []string, hostedZones map[string]string) error { if len(hosts) == 0 { return nil } @@ -432,8 +421,7 @@ func (d *Distribution) checkPendingDNSRecords(hosts []string, hostedZones map[st return fmt.Errorf("error getting hosted zone records: %w", err) } - re := regexp.MustCompile(`(\S+)\.`) - matches := re.FindAllString(domains, -1) + matches := recordSetsRegex.FindAllString(domains, -1) if slices.DisjointTransform( hosts, diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index d6ed0ae5a..fe3ffbdeb 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -12,8 +12,11 @@ import ( "github.com/sighupio/furyctl/internal/tool/kubectl" execx "github.com/sighupio/furyctl/internal/x/exec" + "github.com/sighupio/furyctl/internal/x/slices" ) +var logRegex = regexp.MustCompile(`'(.*?)'`) + type Ingress struct { Name string Host []string @@ -23,10 +26,18 @@ type Client struct { kubeRunner *kubectl.Runner } -func NewClient(binPath, workDir, kubeconfig string, serverSide, skipNotFound, clientVersion bool) *Client { +func NewClient( + binPath, + workDir, + kubeconfig string, + serverSide, + skipNotFound, + clientVersion bool, + executor execx.Executor, +) *Client { return &Client{ kubeRunner: kubectl.NewRunner( - execx.NewStdExecutor(), + executor, kubectl.Paths{ Kubectl: binPath, WorkDir: workDir, @@ -49,9 +60,7 @@ func (c *Client) GetIngresses() ([]Ingress, error) { return result, fmt.Errorf("error while reading resources from cluster: %w", err) } - reg := regexp.MustCompile(`'(.*?)'`) - - logStringIndex := reg.FindStringIndex(log) + logStringIndex := logRegex.FindStringIndex(log) if logStringIndex == nil { return result, nil @@ -75,15 +84,13 @@ func (c *Client) GetPersistentVolumes() ([]string, error) { return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } - reg := regexp.MustCompile(`'(.*?)'`) - - logStringIndex := reg.FindStringIndex(log) + logStringIndex := logRegex.FindStringIndex(log) if logStringIndex == nil { return []string{}, nil } - return cleanStringsSlice(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil + return slices.Clean(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } func (c *Client) GetListOfResourcesNs(ns, resName string) error { @@ -103,15 +110,13 @@ func (c *Client) GetLoadBalancers() ([]string, error) { return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } - reg := regexp.MustCompile(`'(.*?)'`) - - logStringIndex := reg.FindStringIndex(log) + logStringIndex := logRegex.FindStringIndex(log) if logStringIndex == nil { return []string{}, nil } - return cleanStringsSlice(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil + return slices.Clean(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } func (c *Client) DeleteAllResources(res, ns string) (string, error) { @@ -140,15 +145,3 @@ func (c *Client) ToolVersion() (string, error) { return version, nil } - -func cleanStringsSlice(slice []string) []string { - var result []string - - for _, v := range slice { - if v != "" { - result = append(result, v) - } - } - - return result -} diff --git a/internal/kubernetes/client_test.go b/internal/kubernetes/client_test.go new file mode 100644 index 000000000..48636381e --- /dev/null +++ b/internal/kubernetes/client_test.go @@ -0,0 +1,191 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kubernetes_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/kubernetes" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func TestClient_GetPersistentVolumes(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + pvs, err := client.GetPersistentVolumes() + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(pvs) == 0 { + t.Errorf("expected pvs to be not empty") + } +} + +func TestClient_GetLoadBalancers(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + lbs, err := client.GetLoadBalancers() + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(lbs) == 0 { + t.Errorf("expected lbs to be not empty") + } +} + +func TestClient_GetListOfResourcesNs(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + err := client.GetListOfResourcesNs("pod", "default") + if err != nil { + t.Errorf("expected no error, got %v", err) + } +} + +func TestClient_GetIngresses(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + ingresses, err := client.GetIngresses() + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(ingresses) == 0 { + t.Errorf("expected ingresses to be not empty") + } +} + +func TestClient_DeleteFromPath(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + log, err := client.DeleteFromPath("test") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(log) == 0 { + t.Errorf("expected log to be not empty") + } +} + +func TestClient_DeleteAllResources(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + log, err := client.DeleteAllResources("pod", "default") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(log) == 0 { + t.Errorf("expected log to be not empty") + } +} + +func TestClient_ToolVersion(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + version, err := client.ToolVersion() + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(version) == 0 { + t.Errorf("expected version to be not empty") + } +} + +func TestHelperProcess(t *testing.T) { + t.Parallel() + + t.Helper() + + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "kubectl": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "{\n"+ + "\"clientVersion\": {\n"+ + "\"major\": \"1\",\n"+ + "\"minor\": \"21\",\n"+ + "\"gitVersion\": \"v1.21.1\",\n"+ + "\"gitCommit\": \"xxxxx\",\n"+ + "\"gitTreeState\": \"clean\",\n"+ + "\"buildDate\": \"2021-05-12T14:00:00Z\",\n"+ + "\"goVersion\": \"go1.16.4\",\n"+ + "\"compiler\": \"gc\",\n"+ + "\"platform\": \"darwin/amd64\"\n"+ + "}\n"+ + "}\n") + + case "get": + resType := args[6] + switch resType { + case "pv": + fmt.Fprintf(os.Stdout, "'pv-1 pv-2 pv-3'") + + case "svc": + fmt.Fprintf(os.Stdout, "'svc-1 svc-2 svc-3'") + + case "ingress": + fmt.Fprintf(os.Stdout, "'[{\"Name\": \"ingress-1\", \"Host\": [\"host-1\"]},"+ + "{\"Name\": \"ingress-2\", \"Host\": [\"host-2\"]},"+ + "{\"Name\": \"ingress-3\", \"Host\": [\"host-3\"]}]'") + + case "pod": + fmt.Fprintf(os.Stdout, "\"pod\" \"pod-1\" deleted (dry run)\n"+ + "\"pod\" \"pod-2\" deleted (dry run)\n"+ + "\"pod\" \"pod-3\" deleted (dry run)\n") + } + + case "delete": + fmt.Fprintf(os.Stdout, "res \"res-1\" deleted") + } + + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} + +func FakeClient(t *testing.T) *kubernetes.Client { + t.Helper() + + return kubernetes.NewClient( + "kubectl", + "", + "", + true, + true, + true, + execx.NewFakeExecutor(), + ) +} diff --git a/internal/x/slices/clean.go b/internal/x/slices/clean.go new file mode 100644 index 000000000..607087450 --- /dev/null +++ b/internal/x/slices/clean.go @@ -0,0 +1,20 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +// Clean removes all zero values from a slice. +func Clean[T comparable](slice []T) []T { + result := make([]T, 0) + + zeroValue := *new(T) + + for _, v := range slice { + if v != zeroValue { + result = append(result, v) + } + } + + return result +} diff --git a/internal/x/slices/clean_test.go b/internal/x/slices/clean_test.go new file mode 100644 index 000000000..7353a1ca3 --- /dev/null +++ b/internal/x/slices/clean_test.go @@ -0,0 +1,54 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices_test + +import ( + "reflect" + "testing" + + "github.com/sighupio/furyctl/internal/x/slices" +) + +func TestClean(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + s []string + want []string + }{ + { + name: "empty", + s: []string{}, + want: []string{}, + }, + { + name: "one element", + s: []string{"a"}, + want: []string{"a"}, + }, + { + name: "one zero", + s: []string{"a", "", "c"}, + want: []string{"a", "c"}, + }, + { + name: "all zero", + s: []string{"", "", ""}, + want: []string{}, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := slices.Clean(tt.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Clean() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/x/slices/disjoint.go b/internal/x/slices/disjoint.go index 0be7b37c5..74c94689c 100644 --- a/internal/x/slices/disjoint.go +++ b/internal/x/slices/disjoint.go @@ -10,6 +10,7 @@ func identity[T comparable](v T) T { return v } +// Disjoint returns true if the two slices have no elements in common. func Disjoint[T comparable](a, b []T) bool { unique := make(map[T]bool, len(a)) @@ -26,6 +27,8 @@ func Disjoint[T comparable](a, b []T) bool { return true } +// DisjointTransform returns true if the two slices have no elements in common, it takes two +// transform functions that are applied to the elements of each slice before comparing them. func DisjointTransform[T comparable](a, b []T, transformA, transformB TransformFunc[T]) bool { unique := make(map[T]bool, len(a)) From 4dc7729a287e6a51577db37697011d9b4be0e96b Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 4 Apr 2023 11:38:42 +0200 Subject: [PATCH 203/383] chore: post review changes --- internal/kubernetes/client.go | 39 ++++++++++++---------- test/data/e2e/create/cluster/data/kfd.yaml | 2 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index fe3ffbdeb..3be00ab63 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -15,7 +15,8 @@ import ( "github.com/sighupio/furyctl/internal/x/slices" ) -var logRegex = regexp.MustCompile(`'(.*?)'`) +// cmdOutRegex is a regexp used to extract the output of the kubectl command, which is wrapped in single quotes. +var cmdOutRegex = regexp.MustCompile(`'(.*?)'`) type Ingress struct { Name string @@ -50,23 +51,25 @@ func NewClient( } } +// GetIngresses returns a list of ingresses in the cluster, this is done by using the jsonpath format option +// of kubectl to get a valid json output to be unmarshalled. func (c *Client) GetIngresses() ([]Ingress, error) { var result []Ingress - log, err := c.kubeRunner.Get("all", "ingress", "-o", + cmdOut, err := c.kubeRunner.Get("all", "ingress", "-o", "jsonpath='[{range .items[*]}{\"{\"}\"Name\": \"{.metadata.name}\", "+ "\"Host\": [{range .spec.rules[*]}\"{.host}\",{end}]{\"}\"},{end}]'") if err != nil { return result, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := logRegex.FindStringIndex(log) + logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) if logStringIndex == nil { return result, nil } - out := log[logStringIndex[0]+1 : logStringIndex[1]-1] + out := cmdOut[logStringIndex[0]+1 : logStringIndex[1]-1] out = strings.ReplaceAll(out, ",]", "]") @@ -79,18 +82,18 @@ func (c *Client) GetIngresses() ([]Ingress, error) { } func (c *Client) GetPersistentVolumes() ([]string, error) { - log, err := c.kubeRunner.Get("all", "pv", "-o", "jsonpath='{.items[*].metadata.name}'") + cmdOut, err := c.kubeRunner.Get("all", "pv", "-o", "jsonpath='{.items[*].metadata.name}'") if err != nil { return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := logRegex.FindStringIndex(log) + logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) if logStringIndex == nil { return []string{}, nil } - return slices.Clean(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil + return slices.Clean(strings.Split(cmdOut[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } func (c *Client) GetListOfResourcesNs(ns, resName string) error { @@ -104,44 +107,44 @@ func (c *Client) GetListOfResourcesNs(ns, resName string) error { } func (c *Client) GetLoadBalancers() ([]string, error) { - log, err := c.kubeRunner.Get("all", "svc", "-o", + cmdOut, err := c.kubeRunner.Get("all", "svc", "-o", "jsonpath='{.items[?(@.spec.type==\"LoadBalancer\")].metadata.name}'") if err != nil { return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := logRegex.FindStringIndex(log) + logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) if logStringIndex == nil { return []string{}, nil } - return slices.Clean(strings.Split(log[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil + return slices.Clean(strings.Split(cmdOut[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } func (c *Client) DeleteAllResources(res, ns string) (string, error) { - result, err := c.kubeRunner.DeleteAllResources(res, ns) + cmdOut, err := c.kubeRunner.DeleteAllResources(res, ns) if err != nil { - return result, fmt.Errorf("error while deleting resources from cluster: %w", err) + return cmdOut, fmt.Errorf("error while deleting resources from cluster: %w", err) } - return result, nil + return cmdOut, nil } func (c *Client) DeleteFromPath(path string, params ...string) (string, error) { - result, err := c.kubeRunner.Delete(path, params...) + cmdOut, err := c.kubeRunner.Delete(path, params...) if err != nil { - return result, fmt.Errorf("error while deleting resources from cluster: %w", err) + return cmdOut, fmt.Errorf("error while deleting resources from cluster: %w", err) } - return result, nil + return cmdOut, nil } func (c *Client) ToolVersion() (string, error) { - version, err := c.kubeRunner.Version() + cmdOut, err := c.kubeRunner.Version() if err != nil { return "", fmt.Errorf("error while getting tool version: %w", err) } - return version, nil + return cmdOut, nil } diff --git a/test/data/e2e/create/cluster/data/kfd.yaml b/test/data/e2e/create/cluster/data/kfd.yaml index 46c44ad11..d98d5fc6d 100644 --- a/test/data/e2e/create/cluster/data/kfd.yaml +++ b/test/data/e2e/create/cluster/data/kfd.yaml @@ -7,7 +7,7 @@ modules: auth: v0.0.2 aws: v2.1.0 dr: v1.10.1 - ingress: feat/change-behaviour-on-externaldns-module + ingress: v1.14.1 logging: v3.0.1 monitoring: v2.0.0 opa: v1.7.3 From 3d0b45bb3309c14661463c40a37db9f5213b5e96 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 4 Apr 2023 11:42:31 +0200 Subject: [PATCH 204/383] fix: post rebase --- go.mod | 2 -- go.sum | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 3436fb2a3..e2d4af051 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/sighupio/furyctl go 1.19 -replace github.com/sighupio/fury-distribution => ../../distribution - require ( github.com/Al-Pragliola/go-version v1.6.2 github.com/Masterminds/sprig/v3 v3.2.3 diff --git a/go.sum b/go.sum index 6f4cc5164..91ba7481d 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,12 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 h1:s6lZw+WzoqkAfOOKMeWaCH2P2y/vi7t7jG5FvG0crV8= github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= +github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:TjnYLyTTEU4Vr+WQbt2kuE5snc819hLw4YTgIqBB8PQ= +github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.1-0.20230403142615-22072f3cb74d h1:faLdGrMa4QQjGTz1kdJXwdpj4uEN9pc2dzLkEfPpGEE= +github.com/sighupio/fury-distribution v1.25.1-0.20230403142615-22072f3cb74d/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From c36ecd1fad6a6d84b877c9f6cab2674d3a8fcbf7 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 4 Apr 2023 17:35:02 +0200 Subject: [PATCH 205/383] fix(tests): improve accuracy of kubernetes client tests --- .../kfd/v1alpha2/eks/delete/distribution.go | 14 +-- internal/kubernetes/client.go | 32 +++++-- internal/kubernetes/client_test.go | 87 ++++++++++++------- internal/tool/kubectl/runner.go | 2 +- 4 files changed, 92 insertions(+), 43 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index c473b61d0..c8a22d36d 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -149,31 +149,31 @@ func (d *Distribution) Exec() error { logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") - if err = d.kubeClient.GetListOfResourcesNs("all", "ingress"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("ingress", "all"); err != nil { logrus.Errorf("error while getting list of ingress resources: %v", err) } - if err = d.kubeClient.GetListOfResourcesNs("monitoring", "prometheus"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("prometheus", "monitoring"); err != nil { logrus.Errorf("error while getting list of prometheus resources: %v", err) } - if err = d.kubeClient.GetListOfResourcesNs("monitoring", "persistentvolumeclaim"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("persistentvolumeclaim", "monitoring"); err != nil { logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) } - if err = d.kubeClient.GetListOfResourcesNs("logging", "persistentvolumeclaim"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("persistentvolumeclaim", "logging"); err != nil { logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) } - if err = d.kubeClient.GetListOfResourcesNs("logging", "statefulset"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("statefulset", "logging"); err != nil { logrus.Errorf("error while getting list of statefulset resources: %v", err) } - if err = d.kubeClient.GetListOfResourcesNs("logging", "logging"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("logging", "logging"); err != nil { logrus.Errorf("error while getting list of logging resources: %v", err) } - if err = d.kubeClient.GetListOfResourcesNs("ingress-nginx", "service"); err != nil { + if _, err := d.kubeClient.ListNamespaceResources("service", "ingress-nginx"); err != nil { logrus.Errorf("error while getting list of service resources: %v", err) } diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 3be00ab63..1fe4973b1 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -18,6 +18,11 @@ import ( // cmdOutRegex is a regexp used to extract the output of the kubectl command, which is wrapped in single quotes. var cmdOutRegex = regexp.MustCompile(`'(.*?)'`) +type Resource struct { + Name string + Kind string +} + type Ingress struct { Name string Host []string @@ -96,14 +101,29 @@ func (c *Client) GetPersistentVolumes() ([]string, error) { return slices.Clean(strings.Split(cmdOut[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil } -func (c *Client) GetListOfResourcesNs(ns, resName string) error { - _, err := c.kubeRunner.Get(ns, resName, "-o", - "jsonpath={range .items[*]}{\""+resName+" \"}\"{.metadata.name}\"{\" deleted (dry run)\"}{\"\\n\"}{end}") +func (c *Client) ListNamespaceResources(resName, ns string) ([]Resource, error) { + var result []Resource + + cmdOut, err := c.kubeRunner.Get(ns, resName, "-o", + `jsonpath='[{range .items[*]}{"{"}"Name": "{.metadata.name}", "Kind": "{.kind}"{"}"},{end}]'`) if err != nil { - return fmt.Errorf("error while reading resources from cluster: %w", err) + return result, fmt.Errorf("error while reading resources from cluster: %w", err) + } + + logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) + if logStringIndex == nil { + return result, nil } - return nil + out := cmdOut[logStringIndex[0]+1 : logStringIndex[1]-1] + + out = strings.ReplaceAll(out, ",]", "]") + + if err := json.Unmarshal([]byte(out), &result); err != nil { + return result, fmt.Errorf("error while unmarshaling json: %w", err) + } + + return result, nil } func (c *Client) GetLoadBalancers() ([]string, error) { @@ -123,7 +143,7 @@ func (c *Client) GetLoadBalancers() ([]string, error) { } func (c *Client) DeleteAllResources(res, ns string) (string, error) { - cmdOut, err := c.kubeRunner.DeleteAllResources(res, ns) + cmdOut, err := c.kubeRunner.DeleteAllResources(ns, res) if err != nil { return cmdOut, fmt.Errorf("error while deleting resources from cluster: %w", err) } diff --git a/internal/kubernetes/client_test.go b/internal/kubernetes/client_test.go index 48636381e..f01ac3088 100644 --- a/internal/kubernetes/client_test.go +++ b/internal/kubernetes/client_test.go @@ -9,93 +9,116 @@ import ( "os" "testing" + "github.com/google/go-cmp/cmp" "github.com/sighupio/furyctl/internal/kubernetes" execx "github.com/sighupio/furyctl/internal/x/exec" ) -func TestClient_GetPersistentVolumes(t *testing.T) { +func TestClient_GetIngresses(t *testing.T) { t.Parallel() client := FakeClient(t) - pvs, err := client.GetPersistentVolumes() + ingresses, err := client.GetIngresses() if err != nil { t.Errorf("expected no error, got %v", err) } - if len(pvs) == 0 { - t.Errorf("expected pvs to be not empty") + wantedIngresses := []kubernetes.Ingress{ + {"ingress-1", []string{"host-1"}}, + {"ingress-2", []string{"host-2"}}, + {"ingress-3", []string{"host-3"}}, + } + if !cmp.Equal(ingresses, wantedIngresses) { + t.Errorf("expected ingresses to be %v, got: %v", wantedIngresses, ingresses) } } -func TestClient_GetLoadBalancers(t *testing.T) { +func TestClient_GetPersistentVolumes(t *testing.T) { t.Parallel() client := FakeClient(t) - lbs, err := client.GetLoadBalancers() + pvs, err := client.GetPersistentVolumes() if err != nil { t.Errorf("expected no error, got %v", err) } - if len(lbs) == 0 { - t.Errorf("expected lbs to be not empty") + if len(pvs) == 0 { + t.Errorf("expected pvs to be not empty") + } + + wantPvs := []string{"pv-1", "pv-2", "pv-3"} + if !cmp.Equal(pvs, wantPvs) { + t.Errorf("expected pvs to be %v, got: %v", wantPvs, pvs) } } -func TestClient_GetListOfResourcesNs(t *testing.T) { +func TestClient_ListNamespaceResources(t *testing.T) { t.Parallel() client := FakeClient(t) - err := client.GetListOfResourcesNs("pod", "default") + resources, err := client.ListNamespaceResources("pod", "default") if err != nil { t.Errorf("expected no error, got %v", err) } + + wantedResources := []kubernetes.Resource{ + {Kind: "Pod", Name: "pod-1"}, + {Kind: "Pod", Name: "pod-2"}, + {Kind: "Pod", Name: "pod-3"}, + } + if !cmp.Equal(resources, wantedResources) { + t.Errorf("expected resources to be %v, got: %v", wantedResources, resources) + } } -func TestClient_GetIngresses(t *testing.T) { +func TestClient_GetLoadBalancers(t *testing.T) { t.Parallel() client := FakeClient(t) - ingresses, err := client.GetIngresses() + svcs, err := client.GetLoadBalancers() if err != nil { t.Errorf("expected no error, got %v", err) } - if len(ingresses) == 0 { - t.Errorf("expected ingresses to be not empty") + wantSvcs := []string{"svc-1", "svc-2", "svc-3"} + if !cmp.Equal(svcs, wantSvcs) { + t.Errorf("expected svcs to be %v, got: %v", wantSvcs, svcs) } } -func TestClient_DeleteFromPath(t *testing.T) { +func TestClient_DeleteAllResources(t *testing.T) { t.Parallel() client := FakeClient(t) - log, err := client.DeleteFromPath("test") + out, err := client.DeleteAllResources("pod", "default") if err != nil { t.Errorf("expected no error, got %v", err) } - if len(log) == 0 { - t.Errorf("expected log to be not empty") + wantOut := `res "res-1" deleted` + if out != wantOut { + t.Errorf("expected output to be '%s', got: '%s'", wantOut, out) } } -func TestClient_DeleteAllResources(t *testing.T) { +func TestClient_DeleteFromPath(t *testing.T) { t.Parallel() client := FakeClient(t) - log, err := client.DeleteAllResources("pod", "default") + out, err := client.DeleteFromPath("test") if err != nil { t.Errorf("expected no error, got %v", err) } - if len(log) == 0 { - t.Errorf("expected log to be not empty") + wantOut := `res "res-1" deleted` + if out != wantOut { + t.Errorf("expected output to be '%s', got: '%s'", wantOut, out) } } @@ -146,7 +169,11 @@ func TestHelperProcess(t *testing.T) { "}\n") case "get": - resType := args[6] + resType := args[7] + if args[5] == "-A" { + resType = args[6] + } + switch resType { case "pv": fmt.Fprintf(os.Stdout, "'pv-1 pv-2 pv-3'") @@ -155,14 +182,16 @@ func TestHelperProcess(t *testing.T) { fmt.Fprintf(os.Stdout, "'svc-1 svc-2 svc-3'") case "ingress": - fmt.Fprintf(os.Stdout, "'[{\"Name\": \"ingress-1\", \"Host\": [\"host-1\"]},"+ - "{\"Name\": \"ingress-2\", \"Host\": [\"host-2\"]},"+ - "{\"Name\": \"ingress-3\", \"Host\": [\"host-3\"]}]'") + fmt.Fprintf(os.Stdout, + "'[{\"Name\": \"ingress-1\", \"Host\": [\"host-1\"]},"+ + "{\"Name\": \"ingress-2\", \"Host\": [\"host-2\"]},"+ + "{\"Name\": \"ingress-3\", \"Host\": [\"host-3\"]}]'") case "pod": - fmt.Fprintf(os.Stdout, "\"pod\" \"pod-1\" deleted (dry run)\n"+ - "\"pod\" \"pod-2\" deleted (dry run)\n"+ - "\"pod\" \"pod-3\" deleted (dry run)\n") + fmt.Fprintf(os.Stdout, + "'[{\"Name\": \"pod-1\", \"Kind\": \"Pod\"},"+ + "{\"Name\": \"pod-2\", \"Kind\": \"Pod\"},"+ + "{\"Name\": \"pod-3\", \"Kind\": \"Pod\"}]'") } case "delete": diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index bda2ec500..dd431d997 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -99,7 +99,7 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { return out, nil } -func (r *Runner) DeleteAllResources(res, ns string) (string, error) { +func (r *Runner) DeleteAllResources(ns, res string) (string, error) { args := []string{"delete", res, "--all"} if ns != "all" { From d3c6a3200db7f86f6e54c806dbbd81b8a24d0df2 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 4 Apr 2023 17:47:32 +0200 Subject: [PATCH 206/383] chore: fix gci target, gci-lint files, rename k8s client var --- Makefile | 2 +- internal/kubernetes/client.go | 27 ++++++++++++--------------- internal/kubernetes/client_test.go | 1 + 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 724283550..66221753d 100644 --- a/Makefile +++ b/Makefile @@ -122,7 +122,7 @@ gci: @find . -name "*.go" -type f -not -path '*/vendor/*' \ | sed 's/^\.\///g' \ | xargs -I {} sh -c 'echo "formatting imports for {}.." && \ - gci write --skip-generated -s standard,default,"prefix(github.com/sighupio)" {}' + gci write --skip-generated -s standard -s default -s "Prefix(github.com/sighupio)" {}' .PHONY: lint lint-go diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 1fe4973b1..341133b0a 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -68,13 +68,12 @@ func (c *Client) GetIngresses() ([]Ingress, error) { return result, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) - - if logStringIndex == nil { + idx := cmdOutRegex.FindStringIndex(cmdOut) + if idx == nil { return result, nil } - out := cmdOut[logStringIndex[0]+1 : logStringIndex[1]-1] + out := cmdOut[idx[0]+1 : idx[1]-1] out = strings.ReplaceAll(out, ",]", "]") @@ -92,13 +91,12 @@ func (c *Client) GetPersistentVolumes() ([]string, error) { return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) - - if logStringIndex == nil { + idx := cmdOutRegex.FindStringIndex(cmdOut) + if idx == nil { return []string{}, nil } - return slices.Clean(strings.Split(cmdOut[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil + return slices.Clean(strings.Split(cmdOut[idx[0]+1:idx[1]-1], " ")), nil } func (c *Client) ListNamespaceResources(resName, ns string) ([]Resource, error) { @@ -110,12 +108,12 @@ func (c *Client) ListNamespaceResources(resName, ns string) ([]Resource, error) return result, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) - if logStringIndex == nil { + idx := cmdOutRegex.FindStringIndex(cmdOut) + if idx == nil { return result, nil } - out := cmdOut[logStringIndex[0]+1 : logStringIndex[1]-1] + out := cmdOut[idx[0]+1 : idx[1]-1] out = strings.ReplaceAll(out, ",]", "]") @@ -133,13 +131,12 @@ func (c *Client) GetLoadBalancers() ([]string, error) { return []string{}, fmt.Errorf("error while reading resources from cluster: %w", err) } - logStringIndex := cmdOutRegex.FindStringIndex(cmdOut) - - if logStringIndex == nil { + idx := cmdOutRegex.FindStringIndex(cmdOut) + if idx == nil { return []string{}, nil } - return slices.Clean(strings.Split(cmdOut[logStringIndex[0]+1:logStringIndex[1]-1], " ")), nil + return slices.Clean(strings.Split(cmdOut[idx[0]+1:idx[1]-1], " ")), nil } func (c *Client) DeleteAllResources(res, ns string) (string, error) { diff --git a/internal/kubernetes/client_test.go b/internal/kubernetes/client_test.go index f01ac3088..63f5273ac 100644 --- a/internal/kubernetes/client_test.go +++ b/internal/kubernetes/client_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/sighupio/furyctl/internal/kubernetes" execx "github.com/sighupio/furyctl/internal/x/exec" ) From 769e11428469fb78d2a55066a74750a7e82df624 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 6 Apr 2023 11:08:37 +0200 Subject: [PATCH 207/383] chore: update go modules --- go.mod | 8 ++++ go.sum | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e2d4af051..d11a50975 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v1.0.1 github.com/go-playground/validator/v10 v10.11.1 + github.com/gobuffalo/packr/v2 v2.8.3 github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/terraform-json v0.14.0 @@ -44,6 +45,8 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/gobuffalo/logger v1.0.6 // indirect + github.com/gobuffalo/packd v1.0.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect @@ -58,10 +61,14 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jsonmaur/aws-regions/v2 v2.3.1 // indirect + github.com/karrick/godirwalk v1.16.1 // indirect github.com/klauspost/compress v1.15.13 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/markbates/errx v1.1.0 // indirect + github.com/markbates/oncer v1.0.0 // indirect + github.com/markbates/safe v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -90,6 +97,7 @@ require ( golang.org/x/net v0.4.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/tools v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index 91ba7481d..0d8430e62 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,9 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -31,6 +34,7 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/iam v0.9.0 h1:bK6Or6mxhuL8lnj1i9j0yMo2wE/IeTO2cWlfUrf/TZs= cloud.google.com/go/iam v0.9.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= @@ -57,13 +61,19 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.44.163 h1:XO1A/Laqf/l0IxVPghaQzdnVwxofVFv00IlX0BpmbhQ= github.com/aws/aws-sdk-go v1.44.163/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= github.com/briandowns/spinner v1.19.0/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -75,6 +85,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -89,13 +102,16 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -111,6 +127,14 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -124,6 +148,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -140,6 +165,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -159,6 +185,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -174,6 +201,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -186,19 +215,38 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk= github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -209,6 +257,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -217,10 +266,15 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jsonmaur/aws-regions/v2 v2.3.1 h1:WWt452LyhjI4ZCRKBSULVHqIGE8/9UqVQOSAzuc2woE= github.com/jsonmaur/aws-regions/v2 v2.3.1/go.mod h1:NqtmZ2wG5HkrTYFQ+II3BDysj0yek59yjtZjAaCn8lE= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= @@ -239,13 +293,21 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -253,8 +315,10 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -264,34 +328,51 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU= github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M= @@ -302,23 +383,28 @@ github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 h1:s github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:TjnYLyTTEU4Vr+WQbt2kuE5snc819hLw4YTgIqBB8PQ= github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.1-0.20230403142615-22072f3cb74d h1:faLdGrMa4QQjGTz1kdJXwdpj4uEN9pc2dzLkEfPpGEE= -github.com/sighupio/fury-distribution v1.25.1-0.20230403142615-22072f3cb74d/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -335,6 +421,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= @@ -352,6 +439,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -360,17 +448,26 @@ github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uU github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -405,6 +502,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -422,6 +520,8 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -451,9 +551,12 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -469,6 +572,9 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -484,7 +590,9 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -496,6 +604,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -519,14 +629,20 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -541,6 +657,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -562,6 +680,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -571,6 +690,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -593,6 +713,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -601,9 +722,12 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= @@ -632,6 +756,9 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -665,6 +792,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -677,7 +805,13 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -693,9 +827,13 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -718,9 +856,11 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From 46398eda59ea603dccaf5a07ea162beec414ee53 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 6 Apr 2023 11:08:54 +0200 Subject: [PATCH 208/383] chore: move vpn to infra --- internal/apis/kfd/v1alpha2/eks/create/infrastructure.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 6c58528c9..97a8a0669 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -249,10 +249,10 @@ func (i *Infrastructure) createTfVars() error { } func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { - vpnEnabled := (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil) && - (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances == nil) || - (i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances != nil) && - (*i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances > 0) + vpnEnabled := (i.furyctlConf.Spec.Infrastructure.Vpn != nil) && + (i.furyctlConf.Spec.Infrastructure.Vpn.Instances == nil) || + (i.furyctlConf.Spec.Infrastructure.Vpn.Instances != nil) && + (*i.furyctlConf.Spec.Infrastructure.Vpn.Instances > 0) if err := bytesx.SafeWriteToBuffer(buffer, "vpn_enabled = %v\n", vpnEnabled); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) From b5ee5c7d46c77b60d859f6616659334d676d3c76 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 6 Apr 2023 11:13:26 +0200 Subject: [PATCH 209/383] chore: update versions in test --- test/data/e2e/create/cluster/data/kfd.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/data/e2e/create/cluster/data/kfd.yaml b/test/data/e2e/create/cluster/data/kfd.yaml index d98d5fc6d..d124e4b21 100644 --- a/test/data/e2e/create/cluster/data/kfd.yaml +++ b/test/data/e2e/create/cluster/data/kfd.yaml @@ -14,8 +14,8 @@ modules: networking: v1.10.0 kubernetes: eks: - version: 1.24 - installer: feat/137-support-public-cluster + version: 1.25 + installer: develop furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 From f5927c6d9df182b6a76d13d4362642ecf9db5c29 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 14:41:03 +0200 Subject: [PATCH 210/383] feat: support to installer 2.0 infra phase --- go.sum | 2 + .../kfd/v1alpha2/eks/create/infrastructure.go | 57 +++++++++++-------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/go.sum b/go.sum index 0d8430e62..fc44f95d3 100644 --- a/go.sum +++ b/go.sum @@ -386,6 +386,8 @@ github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.m github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:TjnYLyTTEU4Vr+WQbt2kuE5snc819hLw4YTgIqBB8PQ= github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406085729-b30ac61de947 h1:vzKL1O8tXjLaEJqWYgj7ZS1FKFtt0Whj5odqMtU2F/w= +github.com/sighupio/fury-distribution v1.25.2-0.20230406085729-b30ac61de947/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 97a8a0669..d3d7b86e6 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -191,17 +191,40 @@ func (i *Infrastructure) copyFromTemplate() error { func (i *Infrastructure) createTfVars() error { var buffer bytes.Buffer - err := bytesx.SafeWriteToBuffer(&buffer, "name = \"%v\"\n", i.furyctlConf.Metadata.Name) - if err != nil { + if i.furyctlConf.Spec.Infrastructure.Vpc != nil { + if err := i.addVpcDataToTfVars(&buffer); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + if i.furyctlConf.Spec.Infrastructure.Vpn != nil { + if err := i.addVpnDataToTfVars(&buffer); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + return i.writeTfVars(buffer) +} + +func (i *Infrastructure) addVpcDataToTfVars(buffer *bytes.Buffer) error { + vpcEnabled := i.furyctlConf.Spec.Infrastructure.Vpc != nil + + if err := bytesx.SafeWriteToBuffer(buffer, + "vpc_enabled = %v\n", + vpcEnabled, + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + if err := bytesx.SafeWriteToBuffer(buffer, "name = \"%v\"\n", i.furyctlConf.Metadata.Name); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - err = bytesx.SafeWriteToBuffer( - &buffer, - "network_cidr = \"%v\"\n", + if err := bytesx.SafeWriteToBuffer( + buffer, + "cidr = \"%v\"\n", i.furyctlConf.Spec.Infrastructure.Vpc.Network.Cidr, - ) - if err != nil { + ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } @@ -217,35 +240,21 @@ func (i *Infrastructure) createTfVars() error { privateSubnetworkCidrs[i] = fmt.Sprintf("\"%v\"", cidr) } - if err := bytesx.SafeWriteToBuffer(&buffer, - "vpc_enabled = %v\n", - i.furyctlConf.Spec.Infrastructure.Vpc != nil, - ); err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - - if err = bytesx.SafeWriteToBuffer(&buffer, + if err := bytesx.SafeWriteToBuffer(buffer, "vpc_public_subnetwork_cidrs = [%v]\n", strings.Join(publicSubnetworkCidrs, ","), ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - if err := bytesx.SafeWriteToBuffer(&buffer, + if err := bytesx.SafeWriteToBuffer(buffer, "vpc_private_subnetwork_cidrs = [%v]\n", strings.Join(privateSubnetworkCidrs, ","), ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - if i.furyctlConf.Spec.Infrastructure.Vpn != nil { - err = i.addVpnDataToTfVars(&buffer) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } - } - - return i.writeTfVars(buffer) + return nil } func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { From 9f5824403e22f702ed10c37e52320e12cee75754 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 6 Apr 2023 15:36:37 +0200 Subject: [PATCH 211/383] feat: support eks installer 2.0 on bootstrap templates --- .../provisioners/bootstrap/aws/main.tf.tpl | 11 +++++----- .../provisioners/bootstrap/aws/variables.tf | 20 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index df64b405d..06c3526e5 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -34,10 +34,10 @@ module "vpc" { count = var.vpc_enabled ? 1 : 0 - name = var.name - network_cidr = var.network_cidr - tags = var.tags + name = var.name + tags = var.tags + cidr = var.cidr private_subnetwork_cidrs = var.vpc_private_subnetwork_cidrs public_subnetwork_cidrs = var.vpc_public_subnetwork_cidrs } @@ -47,9 +47,8 @@ module "vpn" { count = var.vpn_enabled ? 1 : 0 - name = var.name - network_cidr = var.network_cidr - tags = var.tags + name = var.name + tags = var.tags vpc_id = var.vpc_enabled ? one(module.vpc[*].vpc_id) : var.vpn_vpc_id public_subnets = var.vpc_enabled ? one(module.vpc[*].public_subnets) : var.vpn_public_subnets diff --git a/configs/provisioners/bootstrap/aws/variables.tf b/configs/provisioners/bootstrap/aws/variables.tf index 2a0f49bb6..c06d73edf 100644 --- a/configs/provisioners/bootstrap/aws/variables.tf +++ b/configs/provisioners/bootstrap/aws/variables.tf @@ -9,8 +9,8 @@ variable "name" { type = string } -variable "network_cidr" { - description = "VPC Network CIDR" +variable "cidr" { + description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overridden" type = string } @@ -22,8 +22,8 @@ variable "tags" { variable "vpc_enabled" { description = "Enable VPC creation" - type = bool - default = true + type = bool + default = true } variable "vpc_public_subnetwork_cidrs" { @@ -40,20 +40,20 @@ variable "vpc_private_subnetwork_cidrs" { variable "vpn_enabled" { description = "Enable VPN" - type = bool - default = true + type = bool + default = true } variable "vpn_vpc_id" { description = "ID of the VPC" - type = string - default = "" + type = string + default = "" } variable "vpn_public_subnets" { description = "Enable VPC" - type = list(string) - default = [] + type = list(string) + default = [] } variable "vpn_subnetwork_cidr" { From ded7a7b1f49bd388a5092380bd8e53f310c337a1 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 16:57:41 +0200 Subject: [PATCH 212/383] feat: upgrade tfvar map from schema to 2.0 eks installer --- go.sum | 8 + .../kfd/v1alpha2/eks/create/kubernetes.go | 310 ++++++++++++------ 2 files changed, 226 insertions(+), 92 deletions(-) diff --git a/go.sum b/go.sum index fc44f95d3..203a755b1 100644 --- a/go.sum +++ b/go.sum @@ -388,6 +388,14 @@ github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:T github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sighupio/fury-distribution v1.25.2-0.20230406085729-b30ac61de947 h1:vzKL1O8tXjLaEJqWYgj7ZS1FKFtt0Whj5odqMtU2F/w= github.com/sighupio/fury-distribution v1.25.2-0.20230406085729-b30ac61de947/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406132837-a88691e36d0c h1:pYTEMTIdn7B68RHJ1Ik2fe6GDL2KGoIBbpX9ZKObcV8= +github.com/sighupio/fury-distribution v1.25.2-0.20230406132837-a88691e36d0c/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406141838-891019be97a1 h1:5MfbCOGD6oApCtwSOlJlwgFUhp57qvepskcIZe2z0w0= +github.com/sighupio/fury-distribution v1.25.2-0.20230406141838-891019be97a1/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406142217-71f83c049095 h1:rwG9tEnN4Js32HtH42wcBPdgmi2sLlM/OF+km+kenmU= +github.com/sighupio/fury-distribution v1.25.2-0.20230406142217-71f83c049095/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230406145107-e49f11bd25f2 h1:492Mhsm4NiBoPbPjw/dhj+QZnzrI/Qy1nlbsp+AqbIk= +github.com/sighupio/fury-distribution v1.25.2-0.20230406145107-e49f11bd25f2/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index af0faf26c..84b17c914 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -350,56 +350,58 @@ func (k *Kubernetes) createTfVars() error { allowedClusterEndpointPrivateAccessCIDRs := k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccessCidrs allowedClusterEndpointPublicAccessCIDRs := k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccessCidrs - if infraOutJSON, err := os.ReadFile(path.Join(k.infraOutputsPath, "output.json")); err == nil { - var infraOut terraform.OutputJSON + if k.furyctlConf.Spec.Infrastructure.Vpc != nil { + if infraOutJSON, err := os.ReadFile(path.Join(k.infraOutputsPath, "output.json")); err == nil { + var infraOut terraform.OutputJSON - if err := json.Unmarshal(infraOutJSON, &infraOut); err == nil { - if infraOut.Outputs["private_subnets"] == nil { - return errPvtSubnetNotFound - } - - s, ok := infraOut.Outputs["private_subnets"].Value.([]interface{}) - if !ok { - return errPvtSubnetFromOut - } - - if infraOut.Outputs["vpc_id"] == nil { - return ErrVpcIDNotFound - } + if err := json.Unmarshal(infraOutJSON, &infraOut); err == nil { + if infraOut.Outputs["private_subnets"] == nil { + return errPvtSubnetNotFound + } - v, ok := infraOut.Outputs["vpc_id"].Value.(string) - if !ok { - return ErrVpcIDFromOut - } + s, ok := infraOut.Outputs["private_subnets"].Value.([]interface{}) + if !ok { + return errPvtSubnetFromOut + } - if infraOut.Outputs["vpc_cidr_block"] == nil { - return errVpcCIDRNotFound - } + if infraOut.Outputs["vpc_id"] == nil { + return ErrVpcIDNotFound + } - c, ok := infraOut.Outputs["vpc_cidr_block"].Value.(string) - if !ok { - return errVpcCIDRFromOut - } + v, ok := infraOut.Outputs["vpc_id"].Value.(string) + if !ok { + return ErrVpcIDFromOut + } - subs := make([]private.TypesAwsSubnetId, len(s)) + if infraOut.Outputs["vpc_cidr_block"] == nil { + return errVpcCIDRNotFound + } - for i, sub := range s { - ss, ok := sub.(string) + c, ok := infraOut.Outputs["vpc_cidr_block"].Value.(string) if !ok { - return errPvtSubnetFromOut + return errVpcCIDRFromOut } - subs[i] = private.TypesAwsSubnetId(ss) - } + subs := make([]private.TypesAwsSubnetId, len(s)) - subnetIdsSource = subs - vpcID := private.TypesAwsVpcId(v) - vpcIDSource = &vpcID + for i, sub := range s { + ss, ok := sub.(string) + if !ok { + return errPvtSubnetFromOut + } - allowedClusterEndpointPrivateAccessCIDRs = append( - allowedClusterEndpointPrivateAccessCIDRs, - private.TypesCidr(c), - ) + subs[i] = private.TypesAwsSubnetId(ss) + } + + subnetIdsSource = subs + vpcID := private.TypesAwsVpcId(v) + vpcIDSource = &vpcID + + allowedClusterEndpointPrivateAccessCIDRs = append( + allowedClusterEndpointPrivateAccessCIDRs, + private.TypesCidr(c), + ) + } } } @@ -508,7 +510,7 @@ func (k *Kubernetes) createTfVars() error { err = bytesx.SafeWriteToBuffer( &buffer, - "network = \"%v\"\n", + "vpc_id = \"%v\"\n", *vpcIDSource, ) if err != nil { @@ -523,7 +525,7 @@ func (k *Kubernetes) createTfVars() error { err = bytesx.SafeWriteToBuffer( &buffer, - "subnetworks = [%v]\n", + "subnets = [%v]\n", strings.Join(subnetIds, ","), ) if err != nil { @@ -582,7 +584,7 @@ func (k *Kubernetes) createTfVars() error { if np.Ami != nil { err = bytesx.SafeWriteToBuffer( &buffer, - "os = \"%v\"\n", + "ami_id = \"%v\"\n", np.Ami.Id, ) if err != nil { @@ -605,6 +607,17 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + if np.ContainerRuntime != nil { + err = bytesx.SafeWriteToBuffer( + &buffer, + "container_runtime = \"%v\"\n", + *np.ContainerRuntime, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + err = bytesx.SafeWriteToBuffer( &buffer, "min_size = %v\n", @@ -641,7 +654,7 @@ func (k *Kubernetes) createTfVars() error { err = bytesx.SafeWriteToBuffer( &buffer, - "eks_target_group_arns = [%v]\n", + "target_group_arns = [%v]\n", strings.Join(attachedTargetGroups, ","), ) if err != nil { @@ -678,7 +691,7 @@ func (k *Kubernetes) createTfVars() error { err = bytesx.SafeWriteToBuffer( &buffer, - "subnetworks = [%v]\n", + "subnets = [%v]\n", strings.Join(npSubNetIds, ","), ) if err != nil { @@ -687,7 +700,7 @@ func (k *Kubernetes) createTfVars() error { } else { err = bytesx.SafeWriteToBuffer( &buffer, - "subnetworks = null\n", + "subnets = null\n", ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -713,7 +726,7 @@ func (k *Kubernetes) createTfVars() error { } else { err = bytesx.SafeWriteToBuffer( &buffer, - "labels = {}\n", + "labels = null\n", ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -732,7 +745,7 @@ func (k *Kubernetes) createTfVars() error { } else { err = bytesx.SafeWriteToBuffer( &buffer, - "taints = []\n", + "taints = null\n", ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -758,7 +771,7 @@ func (k *Kubernetes) createTfVars() error { } else { err = bytesx.SafeWriteToBuffer( &buffer, - "tags = {}\n", + "tags = null\n", ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) @@ -886,77 +899,190 @@ func (k *Kubernetes) addAwsAuthToTfVars(buffer *bytes.Buffer) error { return nil } -func (*Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np private.SpecKubernetesNodePool) error { - if len(np.AdditionalFirewallRules) > 0 { +func (k *Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np private.SpecKubernetesNodePool) error { + if np.AdditionalFirewallRules != nil { err := bytesx.SafeWriteToBuffer( buffer, - "additional_firewall_rules = [\n", + "additional_firewall_rules = {\n", ) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - for i, fwRule := range np.AdditionalFirewallRules { - fwRuleTags := "{}" - - if len(fwRule.Tags) > 0 { - var tags []byte + if len(np.AdditionalFirewallRules.CidrBlocks) > 0 { + if err = k.addCidrBlocksFirewallRules(buffer, np.AdditionalFirewallRules.CidrBlocks); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } - tags, err := json.Marshal(fwRule.Tags) - if err != nil { - return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) - } + if len(np.AdditionalFirewallRules.SourceSecurityGroupId) > 0 { + if err = k.addSourceSecurityGroupIDFirewallRules( + buffer, np.AdditionalFirewallRules.SourceSecurityGroupId, + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } - fwRuleTags = string(tags) + if len(np.AdditionalFirewallRules.Self) > 0 { + if err = k.addSelfFirewallRules(buffer, np.AdditionalFirewallRules.Self); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + } + + err = bytesx.SafeWriteToBuffer( + buffer, + "}\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } else { + err := bytesx.SafeWriteToBuffer( + buffer, + "additional_firewall_rules = null\n", + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } - content := "{\nname = \"%v\"\ndirection = \"%v\"\ncidr_block = %v\nprotocol = \"%v\"\n" + - "ports = \"%v\"\ntags = %v\n}" + return nil +} - if i < len(np.AdditionalFirewallRules)-1 { - content += "," - } +func (*Kubernetes) addCidrBlocksFirewallRules( + buffer *bytes.Buffer, + cb []private.SpecKubernetesNodePoolAdditionalFirewallRuleCidrBlock, +) error { + for i, fwRule := range cb { + fwRuleTags := "{}" - dmzCidrRanges := make([]string, len(fwRule.CidrBlocks)) + if len(fwRule.Tags) > 0 { + var tags []byte - for i, cidr := range fwRule.CidrBlocks { - dmzCidrRanges[i] = fmt.Sprintf("\"%v\"", cidr) + tags, err := json.Marshal(fwRule.Tags) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - cidrRange := "" + fwRuleTags = string(tags) + } - if len(dmzCidrRanges) > 0 { - cidrRange = dmzCidrRanges[0] - } + content := "{\ndescription = \"%v\"\ntype = \"%v\"\ncidr_blocks = %v\nprotocol = \"%v\"\n" + + "from_port = \"%v\"\nto_port = \"%v\"\ntags = %v\n}" - err = bytesx.SafeWriteToBuffer( - buffer, - content, - fwRule.Name, - fwRule.Type, - cidrRange, - fwRule.Protocol, - fmt.Sprintf("%v-%v", fwRule.Ports.From, fwRule.Ports.To), - fwRuleTags, - ) + if i < len(cb)-1 { + content += "," + } + + dmzCidrRanges := make([]string, len(fwRule.CidrBlocks)) + + for i, cidr := range fwRule.CidrBlocks { + dmzCidrRanges[i] = fmt.Sprintf("\"%v\"", cidr) + } + + cidrRange := "" + + if len(dmzCidrRanges) > 0 { + cidrRange = dmzCidrRanges[0] + } + + if err := bytesx.SafeWriteToBuffer( + buffer, + content, + fwRule.Name, + fwRule.Type, + cidrRange, + fwRule.Protocol, + fwRule.Ports.From, + fwRule.Ports.To, + fwRuleTags, + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + return nil +} + +func (*Kubernetes) addSourceSecurityGroupIDFirewallRules( + buffer *bytes.Buffer, + cb []private.SpecKubernetesNodePoolAdditionalFirewallRuleSourceSecurityGroupId, +) error { + for i, fwRule := range cb { + fwRuleTags := "{}" + + if len(fwRule.Tags) > 0 { + var tags []byte + + tags, err := json.Marshal(fwRule.Tags) if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + + fwRuleTags = string(tags) } - err = bytesx.SafeWriteToBuffer( + content := "{\ndescription = \"%v\"\ntype = \"%v\"\nsource_security_group_id = %v\nprotocol = \"%v\"\n" + + "from_port = \"%v\"\nto_port = \"%v\"\ntags = %v\n}" + + if i < len(cb)-1 { + content += "," + } + + if err := bytesx.SafeWriteToBuffer( buffer, - "]\n", - ) - if err != nil { + content, + fwRule.Name, + fwRule.Type, + fwRule.SourceSecurityGroupId, + fwRule.Protocol, + fwRule.Ports.From, + fwRule.Ports.To, + fwRuleTags, + ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - } else { - err := bytesx.SafeWriteToBuffer( + } + + return nil +} + +func (*Kubernetes) addSelfFirewallRules( + buffer *bytes.Buffer, + cb []private.SpecKubernetesNodePoolAdditionalFirewallRuleSelf, +) error { + for i, fwRule := range cb { + fwRuleTags := "{}" + + if len(fwRule.Tags) > 0 { + var tags []byte + + tags, err := json.Marshal(fwRule.Tags) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + + fwRuleTags = string(tags) + } + + content := "{\ndescription = \"%v\"\ntype = \"%v\"\nself = %t\nprotocol = \"%v\"\n" + + "from_port = \"%v\"\nto_port = \"%v\"\ntags = %v\n}" + + if i < len(cb)-1 { + content += "," + } + + if err := bytesx.SafeWriteToBuffer( buffer, - "additional_firewall_rules = []\n", - ) - if err != nil { + content, + fwRule.Name, + fwRule.Type, + fwRule.Self, + fwRule.Protocol, + fwRule.Ports.From, + fwRule.Ports.To, + fwRuleTags, + ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } } From 03e402eb435f8b5bfebeb9ea14746fd226e10b45 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 6 Apr 2023 17:22:24 +0200 Subject: [PATCH 213/383] fix: fw rules to tf var --- .../kfd/v1alpha2/eks/create/kubernetes.go | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 84b17c914..7a7f99276 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -910,23 +910,65 @@ func (k *Kubernetes) addFirewallRulesToNodePool(buffer *bytes.Buffer, np private } if len(np.AdditionalFirewallRules.CidrBlocks) > 0 { + if err := bytesx.SafeWriteToBuffer( + buffer, + "cidr_blocks = [\n", + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + if err = k.addCidrBlocksFirewallRules(buffer, np.AdditionalFirewallRules.CidrBlocks); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + + if err = bytesx.SafeWriteToBuffer( + buffer, + "]\n", + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } } if len(np.AdditionalFirewallRules.SourceSecurityGroupId) > 0 { + if err := bytesx.SafeWriteToBuffer( + buffer, + "source_security_group_id = [\n", + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + if err = k.addSourceSecurityGroupIDFirewallRules( buffer, np.AdditionalFirewallRules.SourceSecurityGroupId, ); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + + if err = bytesx.SafeWriteToBuffer( + buffer, + "]\n", + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } } if len(np.AdditionalFirewallRules.Self) > 0 { + if err := bytesx.SafeWriteToBuffer( + buffer, + "self = [\n", + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + if err = k.addSelfFirewallRules(buffer, np.AdditionalFirewallRules.Self); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + + if err = bytesx.SafeWriteToBuffer( + buffer, + "]\n", + ); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } } err = bytesx.SafeWriteToBuffer( @@ -980,18 +1022,12 @@ func (*Kubernetes) addCidrBlocksFirewallRules( dmzCidrRanges[i] = fmt.Sprintf("\"%v\"", cidr) } - cidrRange := "" - - if len(dmzCidrRanges) > 0 { - cidrRange = dmzCidrRanges[0] - } - if err := bytesx.SafeWriteToBuffer( buffer, content, fwRule.Name, fwRule.Type, - cidrRange, + fmt.Sprintf("[%v]", strings.Join(dmzCidrRanges, ",")), fwRule.Protocol, fwRule.Ports.From, fwRule.Ports.To, From 95fe63c0ceb5cefc1e03bbee4937fb9281db12ad Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 6 Apr 2023 17:48:32 +0200 Subject: [PATCH 214/383] fix(configs): rework terraform eks cluster deps --- configs/provisioners/cluster/eks/data.tf | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/configs/provisioners/cluster/eks/data.tf b/configs/provisioners/cluster/eks/data.tf index 58559e9e9..2f160baa4 100644 --- a/configs/provisioners/cluster/eks/data.tf +++ b/configs/provisioners/cluster/eks/data.tf @@ -5,15 +5,9 @@ */ data "aws_eks_cluster" "fury" { - name = var.cluster_name - depends_on = [ - module.fury - ] + name = module.fury.cluster_id } data "aws_eks_cluster_auth" "fury" { - name = var.cluster_name - depends_on = [ - module.fury - ] + name = module.fury.cluster_id } From 5df01197d549e379db82f857e9f670c50ab5129f Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Fri, 7 Apr 2023 15:36:57 +0200 Subject: [PATCH 215/383] feat: fix eks tf template --- configs/provisioners/cluster/eks/main.tf.tpl | 4 +- configs/provisioners/cluster/eks/variables.tf | 99 +++++++++++++------ 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index 05b682265..b8b8605e0 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -55,8 +55,8 @@ module "fury" { cluster_endpoint_public_access_cidrs = var.cluster_endpoint_public_access_cidrs cluster_endpoint_private_access = var.cluster_endpoint_private_access cluster_endpoint_private_access_cidrs = var.cluster_endpoint_private_access_cidrs - network = var.network - subnetworks = var.subnetworks + vpc_id = var.vpc_id + subnets = var.subnets ssh_public_key = var.ssh_public_key node_pools = var.node_pools node_pools_launch_kind = var.node_pools_launch_kind diff --git a/configs/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf index 573f35e39..4ab9c7480 100644 --- a/configs/provisioners/cluster/eks/variables.tf +++ b/configs/provisioners/cluster/eks/variables.tf @@ -15,36 +15,36 @@ variable "cluster_version" { } variable "cluster_log_retention_days" { - type = number + type = number default = 90 } variable "cluster_endpoint_private_access" { - type = bool + type = bool default = false } variable "cluster_endpoint_private_access_cidrs" { - type = list(string) + type = list(string) default = ["0.0.0.0/0"] } variable "cluster_endpoint_public_access" { - type = bool + type = bool default = false } variable "cluster_endpoint_public_access_cidrs" { - type = list(string) + type = list(string) default = ["0.0.0.0/0"] } -variable "network" { +variable "vpc_id" { type = string - description = "Network where the Kubernetes cluster will be hosted" + description = "VPC ID where the Kubernetes cluster will be hosted" } -variable "subnetworks" { +variable "subnets" { type = list(any) description = "List of subnets where the cluster will be hosted" } @@ -57,35 +57,70 @@ variable "ssh_public_key" { variable "node_pools" { description = "An object list defining node pools configurations" type = list(object({ - name = string - version = string # null to use cluster_version - min_size = number - max_size = number - instance_type = string - spot_instance = bool - container_runtime = optional(string) - os = optional(string) - max_pods = optional(number) # null to use default upstream configuration - volume_size = number - subnetworks = list(string) # null to use default upstream configuration - labels = map(string) - taints = list(string) - tags = map(string) - eks_target_group_arns = optional(list(string)) - additional_firewall_rules = list(object({ - name = string - direction = string - cidr_block = string - protocol = string - ports = string - tags = map(string) - })) + name = string + ami_id = optional(string) + version = optional(string) # null to use cluster_version + min_size = number + max_size = number + instance_type = string + container_runtime = optional(string) + spot_instance = optional(bool) + max_pods = optional(number) # null to use default upstream configuration + volume_size = number + subnets = optional(list(string)) # null to use default upstream configuration + labels = optional(map(string)) + taints = optional(list(string)) + tags = optional(map(string)) + target_group_arns = optional(list(string)) + additional_firewall_rules = optional( + object({ + cidr_blocks = optional( + list( + object({ + description = optional(string) + type = string + cidr_blocks = list(string) + protocol = string + from_port = number + to_port = number + tags = map(string) + }) + ) + ) + source_security_group_id = optional( + list( + object({ + description = optional(string) + type = string + source_security_group_id = string + protocol = string + from_port = number + to_port = number + tags = map(string) + }) + ) + ) + self = optional( + list( + object({ + description = optional(string) + type = string + self = bool + protocol = string + from_port = number + to_port = number + tags = map(string) + }) + ) + ) + }) + ) })) default = [] } variable "node_pools_launch_kind" { - type = string + type = string description = "Choose if the node pools will use launch_configurations, launch_templates or both" } From 0a68157795c3dd57909f83fb9863db18fce2effc Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 7 Apr 2023 16:07:50 +0200 Subject: [PATCH 216/383] fix: removed vpc check if cluster is not private --- .../apis/kfd/v1alpha2/eks/create/kubernetes.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 7a7f99276..495b1b5b7 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -151,18 +151,20 @@ func (k *Kubernetes) Exec() error { return nil } - logrus.Info("Checking connection to the VPC...") + if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess { + logrus.Info("Checking connection to the VPC...") - if err := k.checkVPCConnection(); err != nil { - logrus.Debugf("error checking VPC connection: %v", err) + if err := k.checkVPCConnection(); err != nil { + logrus.Debugf("error checking VPC connection: %v", err) - if k.furyctlConf.Spec.Infrastructure != nil { - if k.furyctlConf.Spec.Infrastructure.Vpn != nil { - return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) + if k.furyctlConf.Spec.Infrastructure != nil { + if k.furyctlConf.Spec.Infrastructure.Vpn != nil { + return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) + } } - } - return fmt.Errorf("%w please check your VPC configuration and try again", errKubeAPIUnreachable) + return fmt.Errorf("%w please check your VPC configuration and try again", errKubeAPIUnreachable) + } } logrus.Info("Creating cloud resources, this could take a while...") From 9c2ae4f4167415e174d2579463a567c180b4ac4b Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Sun, 9 Apr 2023 09:57:47 +0200 Subject: [PATCH 217/383] fix: remove check vpn connection on delete if cluster is not private --- .../apis/kfd/v1alpha2/eks/delete/kubernetes.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 62525dd48..6cd8e74bb 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -100,18 +100,20 @@ func (k *Kubernetes) Exec() error { return nil } - logrus.Info("Checking connection to the VPC...") + if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess { + logrus.Info("Checking connection to the VPC...") - if err := k.checkVPCConnection(); err != nil { - logrus.Debugf("error checking VPC connection: %v", err) + if err := k.checkVPCConnection(); err != nil { + logrus.Debugf("error checking VPC connection: %v", err) - if k.furyctlConf.Spec.Infrastructure != nil { - if k.furyctlConf.Spec.Infrastructure.Vpn != nil { - return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) + if k.furyctlConf.Spec.Infrastructure != nil { + if k.furyctlConf.Spec.Infrastructure.Vpn != nil { + return fmt.Errorf("%w please check your VPN connection and try again", errKubeAPIUnreachable) + } } - } - return fmt.Errorf("%w please check your VPC configuration and try again", errKubeAPIUnreachable) + return fmt.Errorf("%w please check your VPC configuration and try again", errKubeAPIUnreachable) + } } if err := k.tfRunner.Init(); err != nil { From 023f1ec6fe443089356f663a4d9e1acd7e75582e Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 12 Apr 2023 10:40:28 +0200 Subject: [PATCH 218/383] fix: condition on VPC check --- go.mod | 8 - go.sum | 141 ------------------ .../kfd/v1alpha2/eks/create/kubernetes.go | 3 +- .../kfd/v1alpha2/eks/delete/kubernetes.go | 3 +- 4 files changed, 4 insertions(+), 151 deletions(-) diff --git a/go.mod b/go.mod index d11a50975..e2d4af051 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v1.0.1 github.com/go-playground/validator/v10 v10.11.1 - github.com/gobuffalo/packr/v2 v2.8.3 github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/terraform-json v0.14.0 @@ -45,8 +44,6 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/gobuffalo/logger v1.0.6 // indirect - github.com/gobuffalo/packd v1.0.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect @@ -61,14 +58,10 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jsonmaur/aws-regions/v2 v2.3.1 // indirect - github.com/karrick/godirwalk v1.16.1 // indirect github.com/klauspost/compress v1.15.13 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/markbates/errx v1.1.0 // indirect - github.com/markbates/oncer v1.0.0 // indirect - github.com/markbates/safe v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -97,7 +90,6 @@ require ( golang.org/x/net v0.4.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/tools v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index 203a755b1..183fd0a86 100644 --- a/go.sum +++ b/go.sum @@ -17,9 +17,6 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -34,7 +31,6 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/iam v0.9.0 h1:bK6Or6mxhuL8lnj1i9j0yMo2wE/IeTO2cWlfUrf/TZs= cloud.google.com/go/iam v0.9.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= @@ -61,19 +57,13 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.44.163 h1:XO1A/Laqf/l0IxVPghaQzdnVwxofVFv00IlX0BpmbhQ= github.com/aws/aws-sdk-go v1.44.163/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= github.com/briandowns/spinner v1.19.0/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -85,9 +75,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -102,16 +89,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -127,14 +111,6 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -148,7 +124,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -165,7 +140,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -185,7 +159,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -201,8 +174,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -215,38 +186,19 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk= github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -257,7 +209,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -266,15 +217,10 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jsonmaur/aws-regions/v2 v2.3.1 h1:WWt452LyhjI4ZCRKBSULVHqIGE8/9UqVQOSAzuc2woE= github.com/jsonmaur/aws-regions/v2 v2.3.1/go.mod h1:NqtmZ2wG5HkrTYFQ+II3BDysj0yek59yjtZjAaCn8lE= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= @@ -293,21 +239,13 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -315,10 +253,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -328,51 +264,34 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU= github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M= @@ -396,25 +315,19 @@ github.com/sighupio/fury-distribution v1.25.2-0.20230406142217-71f83c049095 h1:r github.com/sighupio/fury-distribution v1.25.2-0.20230406142217-71f83c049095/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sighupio/fury-distribution v1.25.2-0.20230406145107-e49f11bd25f2 h1:492Mhsm4NiBoPbPjw/dhj+QZnzrI/Qy1nlbsp+AqbIk= github.com/sighupio/fury-distribution v1.25.2-0.20230406145107-e49f11bd25f2/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -431,7 +344,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= @@ -449,7 +361,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -458,26 +369,17 @@ github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uU github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -512,7 +414,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -530,8 +431,6 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -561,12 +460,9 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -582,9 +478,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -600,9 +493,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -614,8 +505,6 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -639,20 +528,14 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -667,8 +550,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -690,7 +571,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -700,7 +580,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -723,7 +602,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -732,12 +610,9 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= @@ -766,9 +641,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -802,7 +674,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -815,13 +686,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -837,13 +702,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -866,11 +727,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 495b1b5b7..cc86b449b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -151,7 +151,8 @@ func (k *Kubernetes) Exec() error { return nil } - if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess { + if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess { logrus.Info("Checking connection to the VPC...") if err := k.checkVPCConnection(); err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 6cd8e74bb..42de8560f 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -100,7 +100,8 @@ func (k *Kubernetes) Exec() error { return nil } - if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess { + if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess { logrus.Info("Checking connection to the VPC...") if err := k.checkVPCConnection(); err != nil { From 035bf7fa7ef280bf3208277ad628aad9ccf25ac7 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 12 Apr 2023 14:13:17 +0200 Subject: [PATCH 219/383] chore(deps): tidy up go.sum --- go.sum | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/go.sum b/go.sum index 183fd0a86..6f4cc5164 100644 --- a/go.sum +++ b/go.sum @@ -300,21 +300,6 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 h1:s6lZw+WzoqkAfOOKMeWaCH2P2y/vi7t7jG5FvG0crV8= github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4 h1:8peYcf1C2qnvW5uvAEtXNkk4XdkREOw06xJJSBAJ1Yc= -github.com/sighupio/fury-distribution v1.25.1-0.20230331155315-fe60f05a2af4/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798 h1:TjnYLyTTEU4Vr+WQbt2kuE5snc819hLw4YTgIqBB8PQ= -github.com/sighupio/fury-distribution v1.25.2-0.20230406072728-3ea4811ce798/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230406085729-b30ac61de947 h1:vzKL1O8tXjLaEJqWYgj7ZS1FKFtt0Whj5odqMtU2F/w= -github.com/sighupio/fury-distribution v1.25.2-0.20230406085729-b30ac61de947/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230406132837-a88691e36d0c h1:pYTEMTIdn7B68RHJ1Ik2fe6GDL2KGoIBbpX9ZKObcV8= -github.com/sighupio/fury-distribution v1.25.2-0.20230406132837-a88691e36d0c/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230406141838-891019be97a1 h1:5MfbCOGD6oApCtwSOlJlwgFUhp57qvepskcIZe2z0w0= -github.com/sighupio/fury-distribution v1.25.2-0.20230406141838-891019be97a1/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230406142217-71f83c049095 h1:rwG9tEnN4Js32HtH42wcBPdgmi2sLlM/OF+km+kenmU= -github.com/sighupio/fury-distribution v1.25.2-0.20230406142217-71f83c049095/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230406145107-e49f11bd25f2 h1:492Mhsm4NiBoPbPjw/dhj+QZnzrI/Qy1nlbsp+AqbIk= -github.com/sighupio/fury-distribution v1.25.2-0.20230406145107-e49f11bd25f2/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 700cfc1e6c65a4c975670f9c8b50817af5dd968e Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 12 Apr 2023 16:41:03 +0200 Subject: [PATCH 220/383] fix: use latest fury-distribution next commit --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e2d4af051..757b24d79 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 + github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index 6f4cc5164..9b74cb020 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 h1:s6lZw+WzoqkAfOOKMeWaCH2P2y/vi7t7jG5FvG0crV8= github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44 h1:gZ+DBd2tzTUvte7acG368W/6N4xZ0fC4RH0SY8GHGqE= +github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 1de8bd471ffd4559da0efd8737deea7a47957abe Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 12 Apr 2023 17:57:34 +0200 Subject: [PATCH 221/383] fix: tests --- .../dependencies/tools/test_data/furyctl.yaml | 2 - internal/distribution/download_test.go | 6 +- .../data/e2e/create/cluster/data/furyctl.yaml | 34 +- test/data/e2e/create/cluster/data/kfd.yaml | 22 +- .../public/ekscluster-kfd-v1alpha2.json | 191 +++- test/data/e2e/create/config/default/.DS_Store | Bin 0 -> 6148 bytes .../config/default/data/expected-furyctl.yaml | 80 +- test/data/e2e/create/config/distro/kfd.yaml | 24 +- .../config/ekscluster-kfd-v1alpha2.yaml.tpl | 78 +- .../distro/furyctl-defaults.yaml | 0 .../{v1.24.1 => v1.25.1}/distro/kfd.yaml | 24 +- .../public/ekscluster-kfd-v1alpha2.json | 847 ++++++++++++++---- .../dependencies/v1.25.1}/furyctl.yaml | 28 +- .../template/complex-dry-run/furyctl.yaml | 17 +- .../dump/template/complex-dry-run/kfd.yaml | 24 +- .../e2e/dump/template/complex/furyctl.yaml | 17 +- test/data/e2e/dump/template/complex/kfd.yaml | 24 +- .../kfd.yaml | 24 +- .../dump/template/no-furyctl-yaml/kfd.yaml | 24 +- .../e2e/dump/template/simple-dry-run/kfd.yaml | 24 +- test/data/e2e/dump/template/simple/kfd.yaml | 24 +- .../e2e/validate/config/correct/furyctl.yaml | 7 +- .../data/e2e/validate/config/correct/kfd.yaml | 24 +- .../public/ekscluster-kfd-v1alpha2.json | 191 +++- .../e2e/validate/config/nodistro/furyctl.yaml | 28 +- .../e2e/validate/config/wrong/furyctl.yaml | 28 +- test/data/e2e/validate/config/wrong/kfd.yaml | 24 +- .../public/ekscluster-kfd-v1alpha2.json | 191 +++- .../dependencies/correct/furyctl.yaml | 28 +- .../validate/dependencies/correct/kfd.yaml | 24 +- .../kubectl/{1.24.9 => 1.25.8}/kubectl | 4 +- .../dependencies/missing/furyctl.yaml | 28 +- .../validate/dependencies/missing/kfd.yaml | 24 +- .../validate/dependencies/wrong/furyctl.yaml | 22 +- .../e2e/validate/dependencies/wrong/kfd.yaml | 24 +- .../wrong/kubectl/{1.24.9 => 1.25.8}/kubectl | 0 test/data/expensive/common/data/kfd.yaml | 24 +- .../public/ekscluster-kfd-v1alpha2.json | 477 +++++++++- .../create-complete/data/furyctl.yaml | 2 - .../create-skip-infra/data/furyctl.yaml | 2 - .../create-skip-kube/data/furyctl.yaml | 2 - .../distro/furyctl-defaults.yaml | 0 .../{v1.24.1 => v1.25.1}/distro/kfd.yaml | 24 +- .../public/ekscluster-kfd-v1alpha2.json | 847 ++++++++++++++---- .../v1.25.1}/furyctl.yaml | 28 +- test/e2e/furyctl_test.go | 12 +- 46 files changed, 2770 insertions(+), 809 deletions(-) create mode 100644 test/data/e2e/create/config/default/.DS_Store rename test/data/e2e/download/dependencies/{v1.24.1 => v1.25.1}/distro/furyctl-defaults.yaml (100%) rename test/data/e2e/download/dependencies/{v1.24.1 => v1.25.1}/distro/kfd.yaml (67%) rename test/data/{integration/v1.24.1 => e2e/download/dependencies/v1.25.1}/distro/schemas/public/ekscluster-kfd-v1alpha2.json (67%) rename test/data/{integration/v1.24.1 => e2e/download/dependencies/v1.25.1}/furyctl.yaml (93%) rename test/data/e2e/validate/dependencies/correct/kubectl/{1.24.9 => 1.25.8}/kubectl (85%) rename test/data/e2e/validate/dependencies/wrong/kubectl/{1.24.9 => 1.25.8}/kubectl (100%) rename test/data/integration/{v1.24.1 => v1.25.1}/distro/furyctl-defaults.yaml (100%) rename test/data/integration/{v1.24.1 => v1.25.1}/distro/kfd.yaml (67%) rename test/data/{e2e/download/dependencies/v1.24.1 => integration/v1.25.1}/distro/schemas/public/ekscluster-kfd-v1alpha2.json (67%) rename test/data/{e2e/download/dependencies/v1.24.1 => integration/v1.25.1}/furyctl.yaml (93%) diff --git a/internal/dependencies/tools/test_data/furyctl.yaml b/internal/dependencies/tools/test_data/furyctl.yaml index 4132d43de..335e0cc4a 100644 --- a/internal/dependencies/tools/test_data/furyctl.yaml +++ b/internal/dependencies/tools/test_data/furyctl.yaml @@ -66,7 +66,6 @@ spec: tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: [] - name: worker-eks ami: id: ami-0ab303329574a0338 @@ -87,7 +86,6 @@ spec: tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: [] awsAuth: additionalAccounts: [] users: [] diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index 9c4f45a97..60e2711f0 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -26,19 +26,19 @@ func Test_Downloader_Download(t *testing.T) { desc: "unknown furyctl version", wantApiVer: "kfd.sighup.io/v1alpha2", wantKind: "EKSCluster", - wantDistroVer: "v1.24.1", + wantDistroVer: "v1.25.1", }, { desc: "compatible furyctl version", wantApiVer: "kfd.sighup.io/v1alpha2", wantKind: "EKSCluster", - wantDistroVer: "v1.24.1", + wantDistroVer: "v1.25.1", }, { desc: "older furyctl version", wantApiVer: "kfd.sighup.io/v1alpha2", wantKind: "EKSCluster", - wantDistroVer: "v1.24.1", + wantDistroVer: "v1.25.1", }, } for _, tC := range testCases { diff --git a/test/data/e2e/create/cluster/data/furyctl.yaml b/test/data/e2e/create/cluster/data/furyctl.yaml index e6e34e3e0..37a1786ce 100644 --- a/test/data/e2e/create/cluster/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/data/furyctl.yaml @@ -7,7 +7,7 @@ kind: EKSCluster metadata: name: furyctl-dev-aws spec: - distributionVersion: "v1.24.1" + distributionVersion: "v1.25.1" toolsConfiguration: terraform: state: @@ -50,20 +50,18 @@ spec: kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: [ '0.0.0.0/0' ] + publicAccessCidrs: [ '0.0.0.0/0' ] nodePools: - name: worker - ami: - id: ami-0ab303329574a0338 - owner: "363601582189" size: min: 1 max: 3 - subnetIds: [] instance: type: t3.micro - spot: false - volumeSize: 50 - attachedTargetGroups: [] labels: nodepool: worker node.kubernetes.io/role: worker @@ -72,20 +70,12 @@ spec: tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: [] - name: worker-eks - ami: - id: ami-0ab303329574a0338 - owner: "363601582189" size: min: 1 max: 3 - subnetIds: [] instance: type: t3.micro - spot: false - volumeSize: 50 - attachedTargetGroups: [] labels: nodepool: worker node.kubernetes.io/role: worker @@ -93,19 +83,7 @@ spec: tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: [] - awsAuth: - additionalAccounts: [] - users: [] - roles: [] distribution: - common: - nodeSelector: - node.kubernetes.io/role: infra - tolerations: - - effect: NoSchedule - key: node.kubernetes.io/role - value: infra modules: ingress: baseDomain: internal.fury-demo.sighup.io diff --git a/test/data/e2e/create/cluster/data/kfd.yaml b/test/data/e2e/create/cluster/data/kfd.yaml index d124e4b21..f9c591fe7 100644 --- a/test/data/e2e/create/cluster/data/kfd.yaml +++ b/test/data/e2e/create/cluster/data/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.2 - aws: v2.1.0 - dr: v1.10.1 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 ingress: v1.14.1 - logging: v3.0.1 - monitoring: v2.0.0 - opa: v1.7.3 - networking: v1.10.0 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: version: 1.25 - installer: develop + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,11 +25,11 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: version: 0.15.4 eks: awscli: - version: 2.8.12 + version: "*" diff --git a/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json index 76417d576..f1231a976 100644 --- a/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json @@ -99,7 +99,7 @@ "then": { "properties": { "kubernetes": { - "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + "required": ["vpcId", "subnetIds"] } } }, @@ -113,9 +113,6 @@ }, "subnetIds": { "type": "null" - }, - "apiServerEndpointAccess": { - "type": "null" } } } @@ -542,8 +539,7 @@ }, "required": [ "allowedFromCidrs", - "githubUsersName", - "publicKeys" + "githubUsersName" ] }, @@ -560,8 +556,8 @@ "$ref": "#/$defs/Types.AwsSubnetId" } }, - "apiServerEndpointAccess": { - "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + "apiServer": { + "$ref": "#/$defs/Spec.Kubernetes.APIServer" }, "nodeAllowedSshPublicKey": { "anyOf": [ @@ -595,34 +591,80 @@ } }, "required": [ + "apiServer", "nodeAllowedSshPublicKey", "nodePools", "nodePoolsLaunchKind" ] }, - "Spec.Kubernetes.APIServerEndpointAccess": { + "Spec.Kubernetes.APIServer": { "type": "object", "additionalProperties": false, "properties": { - "type": { - "type": "string", - "enum": [ - "public", - "private", - "public_and_private" - ] + "privateAccess": { + "type": "boolean" }, - "allowedCidrs": { + "privateAccessCidrs": { "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" }, - "minItems": 1 + "minItems": 0 + }, + "publicAccessCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 0 + }, + "publicAccess": { + "type": "boolean" } }, "required": [ - "allowedCidrs", - "type" + "privateAccess", + "publicAccess" + ], + "allOf":[ + { + "if": { + "properties": { + "privateAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "privateAccessCidrs" + ], + "properties": { + "privateAccessCidrs": { + "minItems": 1 + } + } + } + }, + { + "if": { + "properties": { + "publicAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "publicAccessCidrs" + ], + "properties": { + "publicAccessCidrs": { + "minItems": 1 + } + } + } + } ] }, "Spec.Kubernetes.NodePool": { @@ -635,6 +677,10 @@ "ami": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" }, + "containerRuntime": { + "type": "string", + "enum": ["docker", "containerd"] + }, "size": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" }, @@ -663,10 +709,7 @@ } }, "additionalFirewallRules": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" - } + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRules" } }, "required": [ @@ -727,7 +770,34 @@ "min" ] }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "Spec.Kubernetes.NodePool.AdditionalFirewallRules": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock" + }, + "minItems": 1 + }, + "sourceSecurityGroupId": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId" + }, + "minItems": 1 + }, + "self": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self" + }, + "minItems": 1 + } + } + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock": { "type": "object", "additionalProperties": false, "properties": { @@ -763,6 +833,70 @@ "type" ] }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "sourceSecurityGroupId": { + "type": "string" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "sourceSecurityGroupId", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "self": { + "type": "boolean" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "self", + "name", + "ports", + "protocol", + "type" + ] + }, "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { "type": "object", "additionalProperties": false, @@ -1964,12 +2098,7 @@ "ingressClass": { "type": "string" } - }, - "required": [ - "disableAuth", - "host", - "ingressClass" - ] + } } } } diff --git a/test/data/e2e/create/config/default/.DS_Store b/test/data/e2e/create/config/default/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ee771938787a1bb39cadddab0eb4f7463f2ead2b GIT binary patch literal 6148 zcmeHKI|>3Z5S>vG!N$@uSMUZw^aOhW1;s`bM6I{-TprCgpGH?ZZR8D1UNV`NkXP*N zh=|TFo0-T&L`HB!x!KS)+c)o6FCz+si0p zwti-h{ zq5pp-aYY5Fz+Wk#gGIBL<4IXtJCCzkTi`3W<=o+Bm^%f7mt&xpV=Sy3PdzE}ip{ZK V6Wc(iBkpt{e+En!8Ws4p0uNe26{i3I literal 0 HcmV?d00001 diff --git a/test/data/e2e/create/config/default/data/expected-furyctl.yaml b/test/data/e2e/create/config/default/data/expected-furyctl.yaml index 33ed1f6f5..5e9efd7cd 100644 --- a/test/data/e2e/create/config/default/data/expected-furyctl.yaml +++ b/test/data/e2e/create/config/default/data/expected-furyctl.yaml @@ -10,7 +10,7 @@ metadata: name: example spec: # This value defines which KFD version will be installed and in consequence the Kubernetes version to use to create the cluster - distributionVersion: v1.24.1 + distributionVersion: v1.25.1 # This sections defines where to store the terraform state files used by furyctl toolsConfiguration: terraform: @@ -81,6 +81,13 @@ spec: nodePoolsLaunchKind: "launch_templates" # Optional Kubernetes Cluster log retention in days. Defaults to 90 days. # logRetentionDays: 90 + # This map defines the access to the Kubernetes API server + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ['0.0.0.0/0'] + publicAccessCidrs: ['0.0.0.0/0'] + # logRetentionDays: 90 # This array contains the definition of the nodepools in the cluster nodePools: # This is the name of the nodepool @@ -149,7 +156,7 @@ spec: # - example:masters # rolearn: "arn:aws:iam::123456789012:role/k8s-example-role" # Optional. Use when spec.infrastructure is left empty and the VPC is not managed by furyctl - vpcId: "vpc-123456780" + # vpcId: "vpc-123456780" # This section describes how the KFD distribution will be installed distribution: # This common configuration will be applied to all the packages that will be installed in the cluster @@ -192,13 +199,13 @@ spec: # provider can be certManager, secret provider: certManager # if provider is set as secret, this key will be used to create the certificate in the cluster - secret: + # secret: # the certificate file content or you can use the file notation to get the content from a file - cert: "{file://relative/path/to/ssl.crt}" + # cert: "{file://relative/path/to/ssl.crt}" # the key file, a file notation can be used to get the content from a file - key: "{file://relative/path/to/ssl.key}" + # key: "{file://relative/path/to/ssl.key}" # the ca file, a file notation can be used to get the content from a file - ca: "{file://relative/path/to/ssl.ca}" + # ca: "{file://relative/path/to/ssl.ca}" # configuration for the cert-manager package certManager: # the configuration for the clusterIssuer that will be created @@ -223,8 +230,6 @@ spec: name: "internal.example.dev" # defines if we need to create the zone, or if it already exists and we only need to adopt/use it create: false - # This field is ignored, but needed. TBD better validation - vpcId: "dummyvalue" # This section contains all the configurations for the logging module logging: # This optional key is used to override automatic parameters @@ -249,6 +254,15 @@ spec: # host: "" # # the ingressClass can be overridden if needed # ingressClass: "" + # minio: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is minio.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # can be opensearch or loki + type: opensearch # configurations for the opensearch package opensearch: # the type of opensearch to install, can be single or triple @@ -263,6 +277,20 @@ spec: # memory: "" # the PVC size used by opensearch, for each pod storageSize: "150Gi" + # configurations for the minio-ha package + minio: + # the PVC size for each minio disk, 6 disks total + storageSize: "20Gi" + # configurations for the loki package + # loki: + ## optional settings to override requests and limits, common for each component + #resources: + # requests: + # cpu: "" + # memory: "" + # limits: + # cpu: "" + # memory: "" # This section contains all the configurations for the monitoring module monitoring: # This optional key is used to override automatic parameters @@ -339,8 +367,6 @@ spec: eks: # The S3 bucket that will be created to store the backups bucketName: example-velero - # This field is ignored, but needed. TBD better validation - iamRoleArn: arn:aws:iam::123456789012:role/dummy-value # The region where the bucket will be created (can be different from the overall region defined in .spec.region) region: eu-west-1 # This optional key is used to override automatic parameters @@ -406,3 +432,37 @@ spec: # loadAllGroups: false # teamNameField: slug # useLoginAsID: false + # Custom Patches to add or override fields in the generated manifests + #customPatches: + # configMapGenerator: + # - name: a-configmap + # files: + # - /path/to/config.example + # - name: b-configmap + # envs: + # - /path/to/envs.env + # patches: + # - target: + # group: "" + # version: v1 + # kind: Service + # name: cluster-autoscaler + # namespace: kube-system + # path: /path/to/patch.yaml + # patchesStrategicMerge: + # - | + # --- + # apiVersion: v1 + # kind: Service + # metadata: + # labels: + # label1: value1 + # name: cluster-autoscaler + # namespace: kube-system + # secretGenerator: + # - name: a-secret + # files: + # - /path/to/config.example + # - name: b-secret + # envs: + # - /path/to/envs.env diff --git a/test/data/e2e/create/config/distro/kfd.yaml b/test/data/e2e/create/config/distro/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/create/config/distro/kfd.yaml +++ b/test/data/e2e/create/config/distro/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl index e0d79632c..74c4bdf36 100644 --- a/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl +++ b/test/data/e2e/create/config/distro/templates/config/ekscluster-kfd-v1alpha2.yaml.tpl @@ -81,6 +81,13 @@ spec: nodePoolsLaunchKind: "launch_templates" # Optional Kubernetes Cluster log retention in days. Defaults to 90 days. # logRetentionDays: 90 + # This map defines the access to the Kubernetes API server + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ['0.0.0.0/0'] + publicAccessCidrs: ['0.0.0.0/0'] + # logRetentionDays: 90 # This array contains the definition of the nodepools in the cluster nodePools: # This is the name of the nodepool @@ -149,7 +156,7 @@ spec: # - example:masters # rolearn: "arn:aws:iam::123456789012:role/k8s-example-role" # Optional. Use when spec.infrastructure is left empty and the VPC is not managed by furyctl - vpcId: "vpc-123456780" + # vpcId: "vpc-123456780" # This section describes how the KFD distribution will be installed distribution: # This common configuration will be applied to all the packages that will be installed in the cluster @@ -192,13 +199,13 @@ spec: # provider can be certManager, secret provider: certManager # if provider is set as secret, this key will be used to create the certificate in the cluster - secret: + # secret: # the certificate file content or you can use the file notation to get the content from a file - cert: "{file://relative/path/to/ssl.crt}" + # cert: "{file://relative/path/to/ssl.crt}" # the key file, a file notation can be used to get the content from a file - key: "{file://relative/path/to/ssl.key}" + # key: "{file://relative/path/to/ssl.key}" # the ca file, a file notation can be used to get the content from a file - ca: "{file://relative/path/to/ssl.ca}" + # ca: "{file://relative/path/to/ssl.ca}" # configuration for the cert-manager package certManager: # the configuration for the clusterIssuer that will be created @@ -223,8 +230,6 @@ spec: name: "internal.example.dev" # defines if we need to create the zone, or if it already exists and we only need to adopt/use it create: false - # This field is ignored, but needed. TBD better validation - vpcId: "dummyvalue" # This section contains all the configurations for the logging module logging: # This optional key is used to override automatic parameters @@ -249,6 +254,15 @@ spec: # host: "" # # the ingressClass can be overridden if needed # ingressClass: "" + # minio: + # # if authentication is globally enabled, it can be disabled for this ingress. + # disableAuth: false + # # the host can be overridden, by default is minio.{.spec.distribution.modules.ingress.baseDomain} + # host: "" + # # the ingressClass can be overridden if needed + # ingressClass: "" + # can be opensearch or loki + type: opensearch # configurations for the opensearch package opensearch: # the type of opensearch to install, can be single or triple @@ -263,6 +277,20 @@ spec: # memory: "" # the PVC size used by opensearch, for each pod storageSize: "150Gi" + # configurations for the minio-ha package + minio: + # the PVC size for each minio disk, 6 disks total + storageSize: "20Gi" + # configurations for the loki package + # loki: + ## optional settings to override requests and limits, common for each component + #resources: + # requests: + # cpu: "" + # memory: "" + # limits: + # cpu: "" + # memory: "" # This section contains all the configurations for the monitoring module monitoring: # This optional key is used to override automatic parameters @@ -339,8 +367,6 @@ spec: eks: # The S3 bucket that will be created to store the backups bucketName: example-velero - # This field is ignored, but needed. TBD better validation - iamRoleArn: arn:aws:iam::123456789012:role/dummy-value # The region where the bucket will be created (can be different from the overall region defined in .spec.region) region: eu-west-1 # This optional key is used to override automatic parameters @@ -406,3 +432,37 @@ spec: # loadAllGroups: false # teamNameField: slug # useLoginAsID: false + # Custom Patches to add or override fields in the generated manifests + #customPatches: + # configMapGenerator: + # - name: a-configmap + # files: + # - /path/to/config.example + # - name: b-configmap + # envs: + # - /path/to/envs.env + # patches: + # - target: + # group: "" + # version: v1 + # kind: Service + # name: cluster-autoscaler + # namespace: kube-system + # path: /path/to/patch.yaml + # patchesStrategicMerge: + # - | + # --- + # apiVersion: v1 + # kind: Service + # metadata: + # labels: + # label1: value1 + # name: cluster-autoscaler + # namespace: kube-system + # secretGenerator: + # - name: a-secret + # files: + # - /path/to/config.example + # - name: b-secret + # envs: + # - /path/to/envs.env diff --git a/test/data/e2e/download/dependencies/v1.24.1/distro/furyctl-defaults.yaml b/test/data/e2e/download/dependencies/v1.25.1/distro/furyctl-defaults.yaml similarity index 100% rename from test/data/e2e/download/dependencies/v1.24.1/distro/furyctl-defaults.yaml rename to test/data/e2e/download/dependencies/v1.25.1/distro/furyctl-defaults.yaml diff --git a/test/data/e2e/download/dependencies/v1.24.1/distro/kfd.yaml b/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml similarity index 67% rename from test/data/e2e/download/dependencies/v1.24.1/distro/kfd.yaml rename to test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/download/dependencies/v1.24.1/distro/kfd.yaml +++ b/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/integration/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/download/dependencies/v1.25.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 67% rename from test/data/integration/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json rename to test/data/e2e/download/dependencies/v1.25.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json index 37842f285..f1231a976 100644 --- a/test/data/integration/v1.24.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/download/dependencies/v1.25.1/distro/schemas/public/ekscluster-kfd-v1alpha2.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2.json", + "$id": "https://schema.sighup.io/kfd/ekscluster-kfd-v1alpha2-public.json", "description": "A Fury Cluster deployed through AWS's Elastic Kubernetes Service", "type": "object", "properties": { @@ -93,28 +93,13 @@ } } } - }, - { - "properties": { - "infrastructure": { - "properties": { - "vpc": { - "properties": { - "vpn": { - "type": "null" - } - } - } - } - } - } } ] }, "then": { "properties": { "kubernetes": { - "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + "required": ["vpcId", "subnetIds"] } } }, @@ -128,16 +113,173 @@ }, "subnetIds": { "type": "null" - }, - "apiServerEndpointAccess": { - "type": "null" } } } } } }, - + "Spec.Distribution.CustomPatches": { + "type": "object", + "properties": { + "configMapGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ConfigMapGenerator" + }, + "secretGenerator": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.SecretGenerator" + }, + "patches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patches" + }, + "patchesStrategicMerge": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.PatchesStrategicMerge" + } + } + }, + "Spec.Distribution.CustomPatches.ConfigMapGenerator": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" + } + }, + "Spec.Distribution.CustomPatches.SecretGenerator": { + "type": "array", + "items": { + "allOf": [{ "$ref": "#/$defs/Spec.Distribution.CustomPatches.ResourceGenerator" }], + "properties": { + "type": { + "type": "string" + } + } + } + }, + "Spec.Distribution.CustomPatches.ResourceGenerator": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "behavior": { + "type": "string", + "enum": ["create", "replace", "merge"] + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + }, + "envs": { + "type": "array", + "items": { + "type": "string" + } + }, + "literals": { + "type": "array", + "items": { + "type": "string" + } + }, + "namespace": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "labels": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "annotations": { + "$ref": "#/$defs/Types.KubeLabels" + }, + "disableNameSuffixHash": { + "type": "boolean" + }, + "immutable": { + "type": "boolean" + } + } + } + }, + "required": ["name"] + }, + "Spec.Distribution.CustomPatches.Patches": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch" + } + }, + "Spec.Distribution.CustomPatches.PatchesStrategicMerge": { + "type": "array", + "items": { + "type": "string" + } + }, + "Spec.Distribution.CustomPatches.Patch": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches.Patch.Target" + }, + "options": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowNameChange": { + "type": "boolean" + }, + "allowKindChange": { + "type": "boolean" + } + } + }, + "path": { + "type": "string" + }, + "patch": { + "type": "string" + } + }, + "oneOf": [ + { + "required": ["path"] + }, + { + "required": ["patch"] + } + ] + }, + "Spec.Distribution.CustomPatches.Patch.Target": { + "type": "object", + "additionalProperties": false, + "properties": { + "group": { + "type": "string" + }, + "version": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "annotationSelector": { + "type": "string" + } + } + }, "Spec.ToolsConfiguration": { "type": "object", "additionalProperties": false, @@ -179,11 +321,10 @@ "additionalProperties": false, "properties": { "bucketName": { - "type": "string" + "$ref": "#/$defs/Types.AwsS3BucketName" }, "keyPrefix": { - "type": "string", - "maxLength": 37 + "$ref": "#/$defs/Types.AwsS3KeyPrefix" }, "region": { "$ref": "#/$defs/Types.AwsRegion" @@ -202,8 +343,81 @@ "properties": { "vpc": { "$ref": "#/$defs/Spec.Infrastructure.Vpc" + }, + "vpn": { + "$ref": "#/$defs/Spec.Infrastructure.Vpn" } - } + }, + "allOf": [ + { + "if": { + "allOf": [ + { + "properties": { + "vpc": { + "type": "null" + } + } + }, + { + "not": { + "properties": { + "vpn": { + "type": "null" + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "required": ["vpcId"] + } + } + } + }, + { + "if": { + "allOf": [ + { + "not": { + "properties": { + "vpc": { + "type": "null" + } + } + } + }, + { + "not": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] + }, + "then": { + "properties": { + "vpn": { + "properties": { + "vpcId": { + "type": "null" + } + } + } + } + } + } + ] }, "Spec.Infrastructure.Vpc": { "type": "object", @@ -211,9 +425,6 @@ "properties": { "network": { "$ref": "#/$defs/Spec.Infrastructure.Vpc.Network" - }, - "vpn": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn" } }, "required": [ @@ -258,7 +469,7 @@ "public" ] }, - "Spec.Infrastructure.Vpc.Vpn": { + "Spec.Infrastructure.Vpn": { "type": "object", "additionalProperties": false, "properties": { @@ -284,7 +495,10 @@ "$ref": "#/$defs/Types.Cidr" }, "ssh": { - "$ref": "#/$defs/Spec.Infrastructure.Vpc.Vpn.Ssh" + "$ref": "#/$defs/Spec.Infrastructure.Vpn.Ssh" + }, + "vpcId": { + "$ref": "#/$defs/Types.AwsVpcId" } }, "required": [ @@ -292,7 +506,7 @@ "vpnClientsSubnetCidr" ] }, - "Spec.Infrastructure.Vpc.Vpn.Ssh": { + "Spec.Infrastructure.Vpn.Ssh": { "type": "object", "additionalProperties": false, "properties": { @@ -313,7 +527,8 @@ "type": "array", "items": { "type": "string" - } + }, + "minItems": 1 }, "allowedFromCidrs": { "type": "array", @@ -324,8 +539,7 @@ }, "required": [ "allowedFromCidrs", - "githubUsersName", - "publicKeys" + "githubUsersName" ] }, @@ -342,19 +556,30 @@ "$ref": "#/$defs/Types.AwsSubnetId" } }, - "apiServerEndpointAccess": { - "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + "apiServer": { + "$ref": "#/$defs/Spec.Kubernetes.APIServer" }, "nodeAllowedSshPublicKey": { "anyOf": [ { - "$ref": "#/$defs/Types.SshPubKey" + "$ref": "#/$defs/Types.AwsSshPubKey" }, { "$ref": "#/$defs/Types.FileRef" } ] }, + "nodePoolsLaunchKind": { + "type": "string", + "enum": [ + "launch_configurations", + "launch_templates", + "both" + ] + }, + "logRetentionDays": { + "type": "integer" + }, "nodePools": { "type": "array", "items": { @@ -366,33 +591,80 @@ } }, "required": [ + "apiServer", "nodeAllowedSshPublicKey", - "nodePools" + "nodePools", + "nodePoolsLaunchKind" ] }, - "Spec.Kubernetes.APIServerEndpointAccess": { + "Spec.Kubernetes.APIServer": { "type": "object", "additionalProperties": false, "properties": { - "type": { - "type": "string", - "enum": [ - "public", - "private", - "public_and_private" - ] + "privateAccess": { + "type": "boolean" }, - "allowedCidrs": { + "privateAccessCidrs": { "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" }, - "minItems": 1 + "minItems": 0 + }, + "publicAccessCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 0 + }, + "publicAccess": { + "type": "boolean" } }, "required": [ - "allowedCidrs", - "type" + "privateAccess", + "publicAccess" + ], + "allOf":[ + { + "if": { + "properties": { + "privateAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "privateAccessCidrs" + ], + "properties": { + "privateAccessCidrs": { + "minItems": 1 + } + } + } + }, + { + "if": { + "properties": { + "publicAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "publicAccessCidrs" + ], + "properties": { + "publicAccessCidrs": { + "minItems": 1 + } + } + } + } ] }, "Spec.Kubernetes.NodePool": { @@ -405,6 +677,10 @@ "ami": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" }, + "containerRuntime": { + "type": "string", + "enum": ["docker", "containerd"] + }, "size": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" }, @@ -433,14 +709,10 @@ } }, "additionalFirewallRules": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" - } + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRules" } }, "required": [ - "ami", "instance", "name", "size" @@ -498,7 +770,34 @@ "min" ] }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "Spec.Kubernetes.NodePool.AdditionalFirewallRules": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock" + }, + "minItems": 1 + }, + "sourceSecurityGroupId": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId" + }, + "minItems": 1 + }, + "self": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self" + }, + "minItems": 1 + } + } + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock": { "type": "object", "additionalProperties": false, "properties": { @@ -516,7 +815,72 @@ "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" - } + }, + "minItems": 1 + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "cidrBlocks", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "sourceSecurityGroupId": { + "type": "string" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "sourceSecurityGroupId", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "self": { + "type": "boolean" }, "protocol": { "$ref": "#/$defs/Types.AwsIpProtocol" @@ -526,7 +890,7 @@ } }, "required": [ - "cidrBlocks", + "self", "name", "ports", "protocol", @@ -629,6 +993,9 @@ }, "modules": { "$ref": "#/$defs/Spec.Distribution.Modules" + }, + "customPatches": { + "$ref": "#/$defs/Spec.Distribution.CustomPatches" } }, "required": [ @@ -748,6 +1115,9 @@ "monitoring": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring" }, + "networking": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking" + }, "policy": { "$ref": "#/$defs/Spec.Distribution.Modules.Policy" } @@ -763,7 +1133,7 @@ "additionalProperties": false, "properties": { "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides" }, "baseDomain": { "type": "string" @@ -777,8 +1147,8 @@ "dns": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS" }, - "externalDns": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ExternalDNS" + "forecastle": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Forecastle" } }, "required": [ @@ -787,6 +1157,24 @@ "nginx" ] }, + "Spec.Distribution.Modules.Ingress.Overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "ingresses": { + "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Overrides.Ingresses" + }, + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Spec.Distribution.Modules.Ingress.Overrides.Ingresses": { "type": "object", "additionalProperties": false, @@ -796,6 +1184,15 @@ } } }, + "Spec.Distribution.Modules.Ingress.Forecastle": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Ingress.Nginx": { "type": "object", "additionalProperties": false, @@ -806,6 +1203,9 @@ }, "tls": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.Nginx.TLS" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -864,6 +1264,9 @@ "properties": { "clusterIssuer": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.CertManager.ClusterIssuer" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -884,9 +1287,6 @@ "type": { "type": "string", "enum": ["dns01", "http01"] - }, - "route53": { - "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53" } }, "required": [ @@ -895,42 +1295,6 @@ "email" ] }, - "Spec.Distribution.Modules.Ingress.ClusterIssuer.Route53": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" - }, - "hostedZoneId": { - "type": "string" - } - }, - "required": [ - "hostedZoneId", - "iamRoleArn", - "region" - ] - }, - "Spec.Distribution.Modules.Ingress.ExternalDNS": { - "type": "object", - "additionalProperties": false, - "properties": { - "privateIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - }, - "publicIamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" - } - }, - "required": [ - "privateIamRoleArn", - "publicIamRoleArn" - ] - }, "Spec.Distribution.Modules.Ingress.DNS": { "type": "object", "additionalProperties": false, @@ -940,6 +1304,9 @@ }, "private": { "$ref": "#/$defs/Spec.Distribution.Modules.Ingress.DNS.Private" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -972,29 +1339,54 @@ }, "create": { "type": "boolean" - }, - "vpcId": { - "type": "string" } }, "required": [ "name", - "create", - "vpcId" + "create" ] }, "Spec.Distribution.Modules.Logging": { "type": "object", "additionalProperties": false, "properties": { + "type": { + "type": "string", + "enum": ["opensearch", "loki"] + }, "overrides": { "$ref": "#/$defs/Types.FuryModuleOverrides" }, "opensearch": { "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Opensearch" + }, + "loki": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Loki" + }, + "cerebro": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Cerebro" + }, + "minio": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Minio" + }, + "operator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Logging.Operator" } }, - "required": ["opensearch"] + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "opensearch" + } + } + }, + "then": { + "required": ["opensearch"] + } + } + ] }, "Spec.Distribution.Modules.Logging.Opensearch": { "type": "object", @@ -1009,12 +1401,54 @@ }, "storageSize": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ "type" ] }, + "Spec.Distribution.Modules.Logging.Cerebro": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Minio": { + "type": "object", + "additionalProperties": false, + "properties": { + "storageSize": { + "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Logging.Loki": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "#/$defs/Types.KubeResources" + } + } + }, + "Spec.Distribution.Modules.Logging.Operator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Monitoring": { "type": "object", "additionalProperties": false, @@ -1027,6 +1461,18 @@ }, "alertmanager": { "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.AlertManager" + }, + "grafana": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.Grafana" + }, + "blackboxExporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.BlackboxExporter" + }, + "kubeStateMetrics": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.KubeStateMetrics" + }, + "x509Exporter": { + "$ref": "#/$defs/Spec.Distribution.Modules.Monitoring.X509Exporter" } } }, @@ -1060,6 +1506,63 @@ } } }, + "Spec.Distribution.Modules.Monitoring.Grafana": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.BlackboxExporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.KubeStateMetrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Monitoring.X509Exporter": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "Spec.Distribution.Modules.Networking": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + }, + "tigeraOperator": { + "$ref": "#/$defs/Spec.Distribution.Modules.Networking.TigeraOperator" + } + } + }, + "Spec.Distribution.Modules.Networking.TigeraOperator": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, "Spec.Distribution.Modules.Policy": { "type": "object", "additionalProperties": false, @@ -1081,6 +1584,9 @@ "items": { "type": "string" } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } } }, @@ -1103,6 +1609,9 @@ "properties": { "eks": { "$ref": "#/$defs/Spec.Distribution.Modules.Dr.Velero.Eks" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["eks"] @@ -1115,16 +1624,13 @@ "$ref": "#/$defs/Types.AwsRegion" }, "bucketName": { - "type": "string" - }, - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "$ref": "#/$defs/Types.AwsS3BucketName", + "maxLength": 49 } }, "required": [ "region", - "bucketName", - "iamRoleArn" + "bucketName" ] }, "Spec.Distribution.Modules.Auth": { @@ -1137,6 +1643,9 @@ "provider": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Provider" }, + "baseDomain": { + "type": "string" + }, "pomerium": { "$ref": "#/$defs/Spec.Distribution.Modules.Auth.Pomerium" }, @@ -1159,11 +1668,7 @@ } }, "then": { - "properties": { - "auth": { - "required": ["dex", "pomerium"] - } - } + "required": ["dex", "pomerium", "baseDomain"] }, "else": { "properties": { @@ -1285,6 +1790,9 @@ }, "policy": { "type": "string" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": [ @@ -1318,6 +1826,9 @@ "properties": { "connectors": { "type": "array" + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } }, "required": ["connectors"] @@ -1326,54 +1837,46 @@ "type": "object", "additionalProperties": false, "properties": { - "overrides": { - "$ref": "#/$defs/Types.FuryModuleOverrides" - }, "clusterAutoscaler": { - "$ref": "#/$defs/Spec.Distribution.Modules.Aws.ClusterAutoScaler" + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } }, "ebsCsiDriver": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] + } }, "loadBalancerController": { "type": "object", "additionalProperties": false, "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" } - }, - "required": [ - "iamRoleArn" - ] - } - } - }, - - "Spec.Distribution.Modules.Aws.ClusterAutoScaler": { - "type": "object", - "additionalProperties": false, - "properties": { - "iamRoleArn": { - "$ref": "#/$defs/Types.AwsArn" + } }, - "region": { - "$ref": "#/$defs/Types.AwsRegion" + "ebsSnapshotController": { + "type": "object", + "additionalProperties": false, + "properties": { + "overrides": { + "$ref": "#/$defs/Types.FuryModuleComponentOverrides" + } + } + }, + "overrides": { + "$ref": "#/$defs/Types.FuryModuleOverrides" } - }, - "required": [ - "iamRoleArn", - "region" - ] + } }, "Types.SemVer": { @@ -1445,6 +1948,10 @@ "type": "string", "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" }, + "Types.AwsSshPubKey": { + "type": "string", + "pattern": "^ssh\\-(ed25519|rsa)\\s+" + }, "Types.AwsSubnetId": { "type": "string", "pattern": "^subnet\\-[0-9a-f]{17}$" @@ -1458,6 +1965,24 @@ "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" }, + "Types.AwsS3BucketName": { + "type": "string", + "allOf": [ + { + "pattern": "^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$" + }, + { + "not": { + "pattern": "^xn--|-s3alias$" + } + } + ] + }, + "Types.AwsS3KeyPrefix": { + "type": "string", + "pattern": "^[A-z0-9][A-z0-9!-_.*'()]+$", + "maxLength": 960 + }, "Types.KubeLabels": { "type": "object", "additionalProperties": { "type": "string" } @@ -1545,6 +2070,21 @@ } } }, + "Types.FuryModuleComponentOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, "Types.FuryModuleOverridesIngress": { "type": "object", "additionalProperties": false, @@ -1558,12 +2098,7 @@ "ingressClass": { "type": "string" } - }, - "required": [ - "disableAuth", - "host", - "ingressClass" - ] + } } } } diff --git a/test/data/integration/v1.24.1/furyctl.yaml b/test/data/e2e/download/dependencies/v1.25.1/furyctl.yaml similarity index 93% rename from test/data/integration/v1.24.1/furyctl.yaml rename to test/data/e2e/download/dependencies/v1.25.1/furyctl.yaml index e8fcaf938..457b2bcef 100644 --- a/test/data/integration/v1.24.1/furyctl.yaml +++ b/test/data/e2e/download/dependencies/v1.25.1/furyctl.yaml @@ -7,7 +7,7 @@ kind: EKSCluster metadata: name: awesome-cluster-staging spec: - distributionVersion: v1.24.1 + distributionVersion: v1.25.1 toolsConfiguration: terraform: state: @@ -53,10 +53,11 @@ spec: - subnet-0ab84702287e38ccb - subnet-0ae4e9199d9192226 - subnet-01787e8da51e4f070 - apiServerEndpointAccess: - type: private - allowedCidrs: - - 10.0.0.0/16 + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ['0.0.0.0/0'] + publicAccessCidrs: ['0.0.0.0/0'] nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" nodePoolsLaunchKind: "launch_templates" nodePools: @@ -83,14 +84,15 @@ spec: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 + cidrBlocks: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 awsAuth: additionalAccounts: - "777777777777" diff --git a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml index b874f7dcb..ed27e5441 100644 --- a/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/furyctl.yaml @@ -104,14 +104,15 @@ spec: k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" # additional rules added to the ASG nodes security group additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 + cidrBlocks: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 # aws-auth configmap definiton awsAuth: # Additional AWS account id to add to the aws-auth configmap, optional diff --git a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/dump/template/complex/furyctl.yaml b/test/data/e2e/dump/template/complex/furyctl.yaml index 79ad667dc..e6207c1c4 100644 --- a/test/data/e2e/dump/template/complex/furyctl.yaml +++ b/test/data/e2e/dump/template/complex/furyctl.yaml @@ -104,14 +104,15 @@ spec: k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" # additional rules added to the ASG nodes security group additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 + cidrBlocks: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 # aws-auth configmap definiton awsAuth: # Additional AWS account id to add to the aws-auth configmap, optional diff --git a/test/data/e2e/dump/template/complex/kfd.yaml b/test/data/e2e/dump/template/complex/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/dump/template/complex/kfd.yaml +++ b/test/data/e2e/dump/template/complex/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml +++ b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml +++ b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml +++ b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/dump/template/simple/kfd.yaml b/test/data/e2e/dump/template/simple/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/dump/template/simple/kfd.yaml +++ b/test/data/e2e/dump/template/simple/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/validate/config/correct/furyctl.yaml b/test/data/e2e/validate/config/correct/furyctl.yaml index e6e34e3e0..e3aa02b43 100644 --- a/test/data/e2e/validate/config/correct/furyctl.yaml +++ b/test/data/e2e/validate/config/correct/furyctl.yaml @@ -50,6 +50,11 @@ spec: kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: [ '0.0.0.0/0' ] + publicAccessCidrs: [ '0.0.0.0/0' ] nodePools: - name: worker ami: @@ -72,7 +77,6 @@ spec: tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: [] - name: worker-eks ami: id: ami-0ab303329574a0338 @@ -93,7 +97,6 @@ spec: tags: k8s.io/cluster-autoscaler/node-template/label/nodepool: "worker" k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" - additionalFirewallRules: [] awsAuth: additionalAccounts: [] users: [] diff --git a/test/data/e2e/validate/config/correct/kfd.yaml b/test/data/e2e/validate/config/correct/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/validate/config/correct/kfd.yaml +++ b/test/data/e2e/validate/config/correct/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json index 76417d576..f1231a976 100644 --- a/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/correct/schemas/public/ekscluster-kfd-v1alpha2.json @@ -99,7 +99,7 @@ "then": { "properties": { "kubernetes": { - "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + "required": ["vpcId", "subnetIds"] } } }, @@ -113,9 +113,6 @@ }, "subnetIds": { "type": "null" - }, - "apiServerEndpointAccess": { - "type": "null" } } } @@ -542,8 +539,7 @@ }, "required": [ "allowedFromCidrs", - "githubUsersName", - "publicKeys" + "githubUsersName" ] }, @@ -560,8 +556,8 @@ "$ref": "#/$defs/Types.AwsSubnetId" } }, - "apiServerEndpointAccess": { - "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + "apiServer": { + "$ref": "#/$defs/Spec.Kubernetes.APIServer" }, "nodeAllowedSshPublicKey": { "anyOf": [ @@ -595,34 +591,80 @@ } }, "required": [ + "apiServer", "nodeAllowedSshPublicKey", "nodePools", "nodePoolsLaunchKind" ] }, - "Spec.Kubernetes.APIServerEndpointAccess": { + "Spec.Kubernetes.APIServer": { "type": "object", "additionalProperties": false, "properties": { - "type": { - "type": "string", - "enum": [ - "public", - "private", - "public_and_private" - ] + "privateAccess": { + "type": "boolean" }, - "allowedCidrs": { + "privateAccessCidrs": { "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" }, - "minItems": 1 + "minItems": 0 + }, + "publicAccessCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 0 + }, + "publicAccess": { + "type": "boolean" } }, "required": [ - "allowedCidrs", - "type" + "privateAccess", + "publicAccess" + ], + "allOf":[ + { + "if": { + "properties": { + "privateAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "privateAccessCidrs" + ], + "properties": { + "privateAccessCidrs": { + "minItems": 1 + } + } + } + }, + { + "if": { + "properties": { + "publicAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "publicAccessCidrs" + ], + "properties": { + "publicAccessCidrs": { + "minItems": 1 + } + } + } + } ] }, "Spec.Kubernetes.NodePool": { @@ -635,6 +677,10 @@ "ami": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" }, + "containerRuntime": { + "type": "string", + "enum": ["docker", "containerd"] + }, "size": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" }, @@ -663,10 +709,7 @@ } }, "additionalFirewallRules": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" - } + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRules" } }, "required": [ @@ -727,7 +770,34 @@ "min" ] }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "Spec.Kubernetes.NodePool.AdditionalFirewallRules": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock" + }, + "minItems": 1 + }, + "sourceSecurityGroupId": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId" + }, + "minItems": 1 + }, + "self": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self" + }, + "minItems": 1 + } + } + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock": { "type": "object", "additionalProperties": false, "properties": { @@ -763,6 +833,70 @@ "type" ] }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "sourceSecurityGroupId": { + "type": "string" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "sourceSecurityGroupId", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "self": { + "type": "boolean" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "self", + "name", + "ports", + "protocol", + "type" + ] + }, "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { "type": "object", "additionalProperties": false, @@ -1964,12 +2098,7 @@ "ingressClass": { "type": "string" } - }, - "required": [ - "disableAuth", - "host", - "ingressClass" - ] + } } } } diff --git a/test/data/e2e/validate/config/nodistro/furyctl.yaml b/test/data/e2e/validate/config/nodistro/furyctl.yaml index 97b71346c..913a33c42 100644 --- a/test/data/e2e/validate/config/nodistro/furyctl.yaml +++ b/test/data/e2e/validate/config/nodistro/furyctl.yaml @@ -9,7 +9,7 @@ metadata: name: awesome-cluster-staging spec: # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: v1.24.1 + distributionVersion: v1.25.1 # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: terraform: @@ -75,10 +75,11 @@ spec: - subnet-0ab84702287e38ccb - subnet-0ae4e9199d9192226 - subnet-01787e8da51e4f070 - apiServerEndpointAccess: - type: private - allowedCidrs: - - 10.0.0.0/16 + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ['0.0.0.0/0'] + publicAccessCidrs: ['0.0.0.0/0'] nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" nodePoolsLaunchKind: "launch_templates" nodePools: @@ -113,14 +114,15 @@ spec: k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" # additional rules added to the ASG nodes security group additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 + cidrBlocks: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 # aws-auth configmap definiton awsAuth: # Additional AWS account id to add to the aws-auth configmap, optional diff --git a/test/data/e2e/validate/config/wrong/furyctl.yaml b/test/data/e2e/validate/config/wrong/furyctl.yaml index 4537ef477..270d6b843 100644 --- a/test/data/e2e/validate/config/wrong/furyctl.yaml +++ b/test/data/e2e/validate/config/wrong/furyctl.yaml @@ -9,7 +9,7 @@ metadata: name: awesome-cluster-staging spec: # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: v1.24.1 + distributionVersion: v1.25.1 test: test # should make the validation fail # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: @@ -76,10 +76,11 @@ spec: - subnet-0ab84702287e38ccb - subnet-0ae4e9199d9192226 - subnet-01787e8da51e4f070 - apiServerEndpointAccess: - type: private - allowedCidrs: - - 10.0.0.0/16 + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ['0.0.0.0/0'] + publicAccessCidrs: ['0.0.0.0/0'] nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" nodePoolsLaunchKind: "launch_templates" nodePools: @@ -114,14 +115,15 @@ spec: k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" # additional rules added to the ASG nodes security group additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 + cidrBlocks: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 # aws-auth configmap definiton awsAuth: # Additional AWS account id to add to the aws-auth configmap, optional diff --git a/test/data/e2e/validate/config/wrong/kfd.yaml b/test/data/e2e/validate/config/wrong/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/validate/config/wrong/kfd.yaml +++ b/test/data/e2e/validate/config/wrong/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json index 76417d576..f1231a976 100644 --- a/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/validate/config/wrong/schemas/public/ekscluster-kfd-v1alpha2.json @@ -99,7 +99,7 @@ "then": { "properties": { "kubernetes": { - "required": ["vpcId", "subnetIds", "apiServerEndpointAccess"] + "required": ["vpcId", "subnetIds"] } } }, @@ -113,9 +113,6 @@ }, "subnetIds": { "type": "null" - }, - "apiServerEndpointAccess": { - "type": "null" } } } @@ -542,8 +539,7 @@ }, "required": [ "allowedFromCidrs", - "githubUsersName", - "publicKeys" + "githubUsersName" ] }, @@ -560,8 +556,8 @@ "$ref": "#/$defs/Types.AwsSubnetId" } }, - "apiServerEndpointAccess": { - "$ref": "#/$defs/Spec.Kubernetes.APIServerEndpointAccess" + "apiServer": { + "$ref": "#/$defs/Spec.Kubernetes.APIServer" }, "nodeAllowedSshPublicKey": { "anyOf": [ @@ -595,34 +591,80 @@ } }, "required": [ + "apiServer", "nodeAllowedSshPublicKey", "nodePools", "nodePoolsLaunchKind" ] }, - "Spec.Kubernetes.APIServerEndpointAccess": { + "Spec.Kubernetes.APIServer": { "type": "object", "additionalProperties": false, "properties": { - "type": { - "type": "string", - "enum": [ - "public", - "private", - "public_and_private" - ] + "privateAccess": { + "type": "boolean" }, - "allowedCidrs": { + "privateAccessCidrs": { "type": "array", "items": { "$ref": "#/$defs/Types.Cidr" }, - "minItems": 1 + "minItems": 0 + }, + "publicAccessCidrs": { + "type": "array", + "items": { + "$ref": "#/$defs/Types.Cidr" + }, + "minItems": 0 + }, + "publicAccess": { + "type": "boolean" } }, "required": [ - "allowedCidrs", - "type" + "privateAccess", + "publicAccess" + ], + "allOf":[ + { + "if": { + "properties": { + "privateAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "privateAccessCidrs" + ], + "properties": { + "privateAccessCidrs": { + "minItems": 1 + } + } + } + }, + { + "if": { + "properties": { + "publicAccess": { + "const": true + } + } + }, + "then": { + "required": [ + "publicAccessCidrs" + ], + "properties": { + "publicAccessCidrs": { + "minItems": 1 + } + } + } + } ] }, "Spec.Kubernetes.NodePool": { @@ -635,6 +677,10 @@ "ami": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Ami" }, + "containerRuntime": { + "type": "string", + "enum": ["docker", "containerd"] + }, "size": { "$ref": "#/$defs/Spec.Kubernetes.NodePool.Size" }, @@ -663,10 +709,7 @@ } }, "additionalFirewallRules": { - "type": "array", - "items": { - "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule" - } + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRules" } }, "required": [ @@ -727,7 +770,34 @@ "min" ] }, - "Spec.Kubernetes.NodePool.AdditionalFirewallRule": { + "Spec.Kubernetes.NodePool.AdditionalFirewallRules": { + "type": "object", + "additionalProperties": false, + "properties": { + "cidrBlocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock" + }, + "minItems": 1 + }, + "sourceSecurityGroupId": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId" + }, + "minItems": 1 + }, + "self": { + "type": "array", + "items": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self" + }, + "minItems": 1 + } + } + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.CidrBlock": { "type": "object", "additionalProperties": false, "properties": { @@ -763,6 +833,70 @@ "type" ] }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.SourceSecurityGroupId": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "sourceSecurityGroupId": { + "type": "string" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "sourceSecurityGroupId", + "name", + "ports", + "protocol", + "type" + ] + }, + "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Self": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ingress", "egress"] + }, + "tags": { + "$ref": "#/$defs/Types.AwsTags" + }, + "self": { + "type": "boolean" + }, + "protocol": { + "$ref": "#/$defs/Types.AwsIpProtocol" + }, + "ports": { + "$ref": "#/$defs/Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports" + } + }, + "required": [ + "self", + "name", + "ports", + "protocol", + "type" + ] + }, "Spec.Kubernetes.NodePool.AdditionalFirewallRule.Ports": { "type": "object", "additionalProperties": false, @@ -1964,12 +2098,7 @@ "ingressClass": { "type": "string" } - }, - "required": [ - "disableAuth", - "host", - "ingressClass" - ] + } } } } diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index 57a2e6202..d1e70f21e 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -9,7 +9,7 @@ metadata: name: awesome-cluster-staging spec: # with this version we will download the kfd.yaml file from the distribution to gather all the other components versions - distributionVersion: v1.24.1 + distributionVersion: v1.25.1 # Under the hood, furyctl uses other tools like terraform, kustomize, etc toolsConfiguration: terraform: @@ -75,10 +75,11 @@ spec: - subnet-0ab84702287e38ccb - subnet-0ae4e9199d9192226 - subnet-01787e8da51e4f070 - apiServerEndpointAccess: - type: private - allowedCidrs: - - 10.0.0.0/16 + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ['0.0.0.0/0'] + publicAccessCidrs: ['0.0.0.0/0'] nodeAllowedSshPublicKey: "ssh-ed25519 XYZ" nodePoolsLaunchKind: "launch_templates" nodePools: @@ -113,14 +114,15 @@ spec: k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "worker" # additional rules added to the ASG nodes security group additionalFirewallRules: - - name: traffic_80_from_172_31_0_0_16 - type: ingress - cidrBlocks: - - 172.31.0.0/16 - protocol: TCP - ports: - from: 80 - to: 80 + cidrBlocks: + - name: traffic_80_from_172_31_0_0_16 + type: ingress + cidrBlocks: + - 172.31.0.0/16 + protocol: TCP + ports: + from: 80 + to: 80 # aws-auth configmap definiton awsAuth: # Additional AWS account id to add to the aws-auth configmap, optional diff --git a/test/data/e2e/validate/dependencies/correct/kfd.yaml b/test/data/e2e/validate/dependencies/correct/kfd.yaml index 332313e8a..f9c591fe7 100644 --- a/test/data/e2e/validate/dependencies/correct/kfd.yaml +++ b/test/data/e2e/validate/dependencies/correct/kfd.yaml @@ -2,20 +2,20 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -version: v1.24.1 +version: v1.25.1 modules: - auth: v0.0.1 - aws: v2.0.0 - dr: v1.9.3 - ingress: v1.12.2 - logging: v2.0.3 - monitoring: v1.14.2 - opa: v1.7.0 - networking: v1.10.0 + auth: v0.0.3 + aws: v2.2.0 + dr: v1.11.0 + ingress: v1.14.1 + logging: v3.1.3 + monitoring: v2.1.0 + opa: v1.8.0 + networking: v1.12.2 kubernetes: eks: - version: 1.24 - installer: v1.10.0 + version: 1.25 + installer: v2.0.0-alpha.1 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -25,7 +25,7 @@ tools: furyagent: version: 0.3.0 kubectl: - version: 1.24.9 + version: 1.25.8 kustomize: version: 3.5.3 terraform: diff --git a/test/data/e2e/validate/dependencies/correct/kubectl/1.24.9/kubectl b/test/data/e2e/validate/dependencies/correct/kubectl/1.25.8/kubectl similarity index 85% rename from test/data/e2e/validate/dependencies/correct/kubectl/1.24.9/kubectl rename to test/data/e2e/validate/dependencies/correct/kubectl/1.25.8/kubectl index ba889a7b9..dfc8a4646 100755 --- a/test/data/e2e/validate/dependencies/correct/kubectl/1.24.9/kubectl +++ b/test/data/e2e/validate/dependencies/correct/kubectl/1.25.8/kubectl @@ -4,8 +4,8 @@ cat < Date: Thu, 13 Apr 2023 10:10:43 +0200 Subject: [PATCH 222/383] fix: e2e tests --- .../kfd/v1alpha2/eks/create/kubernetes.go | 3 +- internal/apis/kfd/v1alpha2/eks/creator.go | 8 +- .../kfd/v1alpha2/eks/delete/kubernetes.go | 3 +- internal/apis/kfd/v1alpha2/eks/deleter.go | 8 +- .../{ => infrastructure}/bin_mock/furyagent | 0 .../{ => infrastructure}/bin_mock/kubectl | 0 .../{ => infrastructure}/bin_mock/kustomize | 0 .../{ => infrastructure}/bin_mock/openvpn | 0 .../{ => infrastructure}/bin_mock/terraform | 0 .../data/furyctl-defaults.yaml | 0 .../{ => infrastructure}/data/furyctl.yaml | 0 .../{ => infrastructure}/data/kfd.yaml | 0 .../public/ekscluster-kfd-v1alpha2.json | 0 .../cluster/kubernetes/bin_mock/furyagent | 5 + .../cluster/kubernetes/bin_mock/kubectl | 5 + .../cluster/kubernetes/bin_mock/kustomize | 5 + .../cluster/kubernetes/bin_mock/openvpn | 9 + .../cluster/kubernetes/bin_mock/terraform | 6 + .../kubernetes/data/furyctl-defaults.yaml | 223 ++ .../cluster/kubernetes/data/furyctl.yaml | 160 ++ .../create/cluster/kubernetes/data/kfd.yaml | 35 + .../public/ekscluster-kfd-v1alpha2.json | 2104 +++++++++++++++++ test/e2e/furyctl_test.go | 14 +- 23 files changed, 2580 insertions(+), 8 deletions(-) rename test/data/e2e/create/cluster/{ => infrastructure}/bin_mock/furyagent (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/bin_mock/kubectl (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/bin_mock/kustomize (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/bin_mock/openvpn (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/bin_mock/terraform (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/data/furyctl-defaults.yaml (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/data/furyctl.yaml (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/data/kfd.yaml (100%) rename test/data/e2e/create/cluster/{ => infrastructure}/data/schemas/public/ekscluster-kfd-v1alpha2.json (100%) create mode 100755 test/data/e2e/create/cluster/kubernetes/bin_mock/furyagent create mode 100755 test/data/e2e/create/cluster/kubernetes/bin_mock/kubectl create mode 100755 test/data/e2e/create/cluster/kubernetes/bin_mock/kustomize create mode 100755 test/data/e2e/create/cluster/kubernetes/bin_mock/openvpn create mode 100755 test/data/e2e/create/cluster/kubernetes/bin_mock/terraform create mode 100644 test/data/e2e/create/cluster/kubernetes/data/furyctl-defaults.yaml create mode 100644 test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml create mode 100644 test/data/e2e/create/cluster/kubernetes/data/kfd.yaml create mode 100644 test/data/e2e/create/cluster/kubernetes/data/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index cc86b449b..62df46f76 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -353,7 +353,8 @@ func (k *Kubernetes) createTfVars() error { allowedClusterEndpointPrivateAccessCIDRs := k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccessCidrs allowedClusterEndpointPublicAccessCIDRs := k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccessCidrs - if k.furyctlConf.Spec.Infrastructure.Vpc != nil { + if k.furyctlConf.Spec.Infrastructure != nil && + k.furyctlConf.Spec.Infrastructure.Vpc != nil { if infraOutJSON, err := os.ReadFile(path.Join(k.infraOutputsPath, "output.json")); err == nil { var infraOut terraform.OutputJSON diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 5269291dc..5d995e433 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -112,6 +112,12 @@ func (v *ClusterCreator) Create(skipPhase string) error { return err } + var vpnConfig *private.SpecInfrastructureVpn + + if v.furyctlConf.Spec.Infrastructure != nil { + vpnConfig = v.furyctlConf.Spec.Infrastructure.Vpn + } + vpnConnector := NewVpnConnector( v.furyctlConf.Metadata.Name, infra.SecretsPath, @@ -119,7 +125,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { v.kfdManifest.Tools.Common.Furyagent.Version, v.vpnAutoConnect, v.skipVpn, - v.furyctlConf.Spec.Infrastructure.Vpn, + vpnConfig, ) switch v.phase { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 42de8560f..a984a0971 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -144,7 +144,8 @@ func (k *Kubernetes) checkVPCConnection() error { var err error - if k.furyctlConf.Spec.Infrastructure != nil { + if k.furyctlConf.Spec.Infrastructure != nil && + k.furyctlConf.Spec.Infrastructure.Vpc != nil { cidr = string(k.furyctlConf.Spec.Infrastructure.Vpc.Network.Cidr) } else { vpcID := k.furyctlConf.Spec.Kubernetes.VpcId diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 3c6316e46..22fb05f49 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -99,6 +99,12 @@ func (d *ClusterDeleter) Delete() error { return fmt.Errorf("error while creating infrastructure phase: %w", err) } + var vpnConfig *private.SpecInfrastructureVpn + + if d.furyctlConf.Spec.Infrastructure != nil { + vpnConfig = d.furyctlConf.Spec.Infrastructure.Vpn + } + vpnConnector := NewVpnConnector( d.furyctlConf.Metadata.Name, infra.SecretsPath, @@ -106,7 +112,7 @@ func (d *ClusterDeleter) Delete() error { d.kfdManifest.Tools.Common.Furyagent.Version, d.vpnAutoConnect, d.skipVpn, - d.furyctlConf.Spec.Infrastructure.Vpn, + vpnConfig, ) switch d.phase { diff --git a/test/data/e2e/create/cluster/bin_mock/furyagent b/test/data/e2e/create/cluster/infrastructure/bin_mock/furyagent similarity index 100% rename from test/data/e2e/create/cluster/bin_mock/furyagent rename to test/data/e2e/create/cluster/infrastructure/bin_mock/furyagent diff --git a/test/data/e2e/create/cluster/bin_mock/kubectl b/test/data/e2e/create/cluster/infrastructure/bin_mock/kubectl similarity index 100% rename from test/data/e2e/create/cluster/bin_mock/kubectl rename to test/data/e2e/create/cluster/infrastructure/bin_mock/kubectl diff --git a/test/data/e2e/create/cluster/bin_mock/kustomize b/test/data/e2e/create/cluster/infrastructure/bin_mock/kustomize similarity index 100% rename from test/data/e2e/create/cluster/bin_mock/kustomize rename to test/data/e2e/create/cluster/infrastructure/bin_mock/kustomize diff --git a/test/data/e2e/create/cluster/bin_mock/openvpn b/test/data/e2e/create/cluster/infrastructure/bin_mock/openvpn similarity index 100% rename from test/data/e2e/create/cluster/bin_mock/openvpn rename to test/data/e2e/create/cluster/infrastructure/bin_mock/openvpn diff --git a/test/data/e2e/create/cluster/bin_mock/terraform b/test/data/e2e/create/cluster/infrastructure/bin_mock/terraform similarity index 100% rename from test/data/e2e/create/cluster/bin_mock/terraform rename to test/data/e2e/create/cluster/infrastructure/bin_mock/terraform diff --git a/test/data/e2e/create/cluster/data/furyctl-defaults.yaml b/test/data/e2e/create/cluster/infrastructure/data/furyctl-defaults.yaml similarity index 100% rename from test/data/e2e/create/cluster/data/furyctl-defaults.yaml rename to test/data/e2e/create/cluster/infrastructure/data/furyctl-defaults.yaml diff --git a/test/data/e2e/create/cluster/data/furyctl.yaml b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml similarity index 100% rename from test/data/e2e/create/cluster/data/furyctl.yaml rename to test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml diff --git a/test/data/e2e/create/cluster/data/kfd.yaml b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml similarity index 100% rename from test/data/e2e/create/cluster/data/kfd.yaml rename to test/data/e2e/create/cluster/infrastructure/data/kfd.yaml diff --git a/test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/create/cluster/infrastructure/data/schemas/public/ekscluster-kfd-v1alpha2.json similarity index 100% rename from test/data/e2e/create/cluster/data/schemas/public/ekscluster-kfd-v1alpha2.json rename to test/data/e2e/create/cluster/infrastructure/data/schemas/public/ekscluster-kfd-v1alpha2.json diff --git a/test/data/e2e/create/cluster/kubernetes/bin_mock/furyagent b/test/data/e2e/create/cluster/kubernetes/bin_mock/furyagent new file mode 100755 index 000000000..71bda34a8 --- /dev/null +++ b/test/data/e2e/create/cluster/kubernetes/bin_mock/furyagent @@ -0,0 +1,5 @@ +#!/bin/sh + +cat < +Compile time defines: enable_async_push=no enable_comp_stub=no enable_crypto_ofb_cfb=yes enable_debug=no enable_def_auth=yes enable_dependency_tracking=no enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown enable_fast_install=needless enable_fragment=yes enable_iproute2=no enable_libtool_lock=yes enable_lz4=yes enable_lzo=yes enable_management=yes enable_multihome=yes enable_pam_dlopen=no enable_pedantic=no enable_pf=yes enable_pkcs11=yes enable_plugin_auth_pam=yes enable_plugin_down_root=yes enable_plugins=yes enable_port_share=yes enable_selinux=no enable_shared=yes enable_shared_with_static_runtimes=no enable_silent_rules=no enable_small=no enable_static=yes enable_strict=no enable_strict_options=no enable_systemd=no enable_werror=no enable_win32_dll=yes enable_x509_alt_username=no with_aix_soname=aix with_crypto_library=openssl with_gnu_ld=no with_mem_check=no with_openssl_engine=auto with_sysroot=no +EOF diff --git a/test/data/e2e/create/cluster/kubernetes/bin_mock/terraform b/test/data/e2e/create/cluster/kubernetes/bin_mock/terraform new file mode 100755 index 000000000..00882a6dc --- /dev/null +++ b/test/data/e2e/create/cluster/kubernetes/bin_mock/terraform @@ -0,0 +1,6 @@ +#!/bin/sh + +cat <[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P[^:\\n]*):(?P(?P[^:\\/\\n]*)[:\\/])?(?P.*)$" + }, + "Types.AwsRegion": { + "type": "string", + "enum": [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ] + }, + "Types.AwsVpcId": { + "type": "string", + "pattern": "^vpc\\-([0-9a-f]{8}|[0-9a-f]{17})$" + }, + "Types.AwsSshPubKey": { + "type": "string", + "pattern": "^ssh\\-(ed25519|rsa)\\s+" + }, + "Types.AwsSubnetId": { + "type": "string", + "pattern": "^subnet\\-[0-9a-f]{17}$" + }, + "Types.AwsTags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.AwsIpProtocol": { + "type": "string", + "pattern": "^(?i)(tcp|udp|icmp|icmpv6|-1)$", + "$comment": "this value should be lowercase, but we rely on terraform to do the conversion to make it a bit more user friendly" + }, + "Types.AwsS3BucketName": { + "type": "string", + "allOf": [ + { + "pattern": "^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$" + }, + { + "not": { + "pattern": "^xn--|-s3alias$" + } + } + ] + }, + "Types.AwsS3KeyPrefix": { + "type": "string", + "pattern": "^[A-z0-9][A-z0-9!-_.*'()]+$", + "maxLength": 960 + }, + "Types.KubeLabels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "Types.KubeTaints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^([a-zA-Z0-9\\-\\.\\/]+)=(\\w+):(NoSchedule|PreferNoSchedule|NoExecute)$" + } + }, + "Types.KubeNodeSelector": { + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, + "Types.KubeToleration": { + "type": "object", + "additionalProperties": false, + "properties": { + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "effect", + "key", + "value" + ] + }, + "Types.KubeResources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "Types.FuryModuleOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + }, + "ingresses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Types.FuryModuleOverridesIngress" + } + } + } + }, + "Types.FuryModuleComponentOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeSelector": { + "$ref": "#/$defs/Types.KubeNodeSelector" + }, + "tolerations": { + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/Types.KubeToleration" + } + } + } + }, + "Types.FuryModuleOverridesIngress": { + "type": "object", + "additionalProperties": false, + "properties": { + "disableAuth": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "ingressClass": { + "type": "string" + } + } + } + } +} diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index 033caaa57..c8bae2abb 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -536,9 +536,12 @@ var ( RestoreEnvVars := BackupEnvVars("PATH") defer RestoreEnvVars() - bp := Abs("../data/e2e/create/cluster/bin_mock") + absBasePath, err := filepath.Abs(filepath.Join(absBasePath, "infrastructure")) + Expect(err).To(Not(HaveOccurred())) + + bp := Abs("../data/e2e/create/cluster/infrastructure/bin_mock") - err := os.Setenv("PATH", bp+":"+os.Getenv("PATH")) + err = os.Setenv("PATH", bp+":"+os.Getenv("PATH")) Expect(err).To(Not(HaveOccurred())) furyctlYamlPath := path.Join(absBasePath, "data/furyctl.yaml") @@ -565,9 +568,12 @@ var ( RestoreEnvVars := BackupEnvVars("PATH") defer RestoreEnvVars() - bp := Abs("../data/e2e/create/cluster/bin_mock") + absBasePath, err := filepath.Abs(filepath.Join(absBasePath, "kubernetes")) + Expect(err).To(Not(HaveOccurred())) + + bp := Abs("../data/e2e/create/cluster/kubernetes/bin_mock") - err := os.Setenv("PATH", bp+":"+os.Getenv("PATH")) + err = os.Setenv("PATH", bp+":"+os.Getenv("PATH")) Expect(err).To(Not(HaveOccurred())) furyctlYamlPath := path.Join(absBasePath, "data/furyctl.yaml") From b5f7d7f7ef1e3d8430e672324df6f88a76891650 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 13 Apr 2023 12:38:52 +0200 Subject: [PATCH 223/383] fix: license banner date --- configs/provisioners/bootstrap/aws/output.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/provisioners/bootstrap/aws/output.tf b/configs/provisioners/bootstrap/aws/output.tf index 62046ad25..780aed4ca 100644 --- a/configs/provisioners/bootstrap/aws/output.tf +++ b/configs/provisioners/bootstrap/aws/output.tf @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 SIGHUP s.r.l All rights reserved. + * Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ From 875a4398c66028298db1f9476a9728702e530d48 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 13 Apr 2023 16:19:30 +0200 Subject: [PATCH 224/383] feat: log details when deleting blocking resources --- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index c8a22d36d..104bbc632 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -204,7 +204,7 @@ func (d *Distribution) Exec() error { } } - logrus.Info("Deleting blocking resources...[PersistentVolumeClaims, StatefulSets, Logging, Prometheus]") + logrus.Info("Deleting blocking resources...") if err = d.deleteBlockingResources(); err != nil { return err @@ -318,31 +318,37 @@ func (d *Distribution) deleteIngresses() error { func (d *Distribution) deleteBlockingResources() error { dur := time.Minute * ingressAfterDeleteDelay + logrus.Info("Deleting prometheus resources...") _, err := d.kubeClient.DeleteAllResources("prometheus", "monitoring") if err != nil { return fmt.Errorf("error deleting prometheus resources: %w", err) } + logrus.Info("Deleting PersistentVolumeClaims in the namespace 'monitoring'...") _, err = d.kubeClient.DeleteAllResources("pvc", "monitoring") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'monitoring': %w", err) } + logrus.Info("Deleting logging resources...") _, err = d.kubeClient.DeleteAllResources("logging", "logging") if err != nil { return fmt.Errorf("error deleting logging resources: %w", err) } + logrus.Info("Deleting StafultSets in the namespace 'logging'...") _, err = d.kubeClient.DeleteAllResources("sts", "logging") if err != nil { return fmt.Errorf("error deleting sts in namespace 'logging': %w", err) } + logrus.Info("Deleting PersistentVolumeClaims in the namespace 'logging'...") _, err = d.kubeClient.DeleteAllResources("pvc", "logging") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'logging': %w", err) } + logrus.Info("Deleting Services in the namespace 'ingress-nginx'...") _, err = d.kubeClient.DeleteAllResources("svc", "ingress-nginx") if err != nil { return fmt.Errorf("error deleting svc in namespace 'ingress-nginx': %w", err) From 3d2341a9a20d9c194d2d7dc27b99bf64b1b537c9 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 13 Apr 2023 16:22:01 +0200 Subject: [PATCH 225/383] fix: linting --- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 104bbc632..697732412 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -319,36 +319,42 @@ func (d *Distribution) deleteBlockingResources() error { dur := time.Minute * ingressAfterDeleteDelay logrus.Info("Deleting prometheus resources...") + _, err := d.kubeClient.DeleteAllResources("prometheus", "monitoring") if err != nil { return fmt.Errorf("error deleting prometheus resources: %w", err) } logrus.Info("Deleting PersistentVolumeClaims in the namespace 'monitoring'...") + _, err = d.kubeClient.DeleteAllResources("pvc", "monitoring") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'monitoring': %w", err) } logrus.Info("Deleting logging resources...") + _, err = d.kubeClient.DeleteAllResources("logging", "logging") if err != nil { return fmt.Errorf("error deleting logging resources: %w", err) } logrus.Info("Deleting StafultSets in the namespace 'logging'...") + _, err = d.kubeClient.DeleteAllResources("sts", "logging") if err != nil { return fmt.Errorf("error deleting sts in namespace 'logging': %w", err) } logrus.Info("Deleting PersistentVolumeClaims in the namespace 'logging'...") + _, err = d.kubeClient.DeleteAllResources("pvc", "logging") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'logging': %w", err) } logrus.Info("Deleting Services in the namespace 'ingress-nginx'...") + _, err = d.kubeClient.DeleteAllResources("svc", "ingress-nginx") if err != nil { return fmt.Errorf("error deleting svc in namespace 'ingress-nginx': %w", err) From 005af941cafe52ec95696ea3d9c498cf9a72559e Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 13 Apr 2023 16:25:36 +0200 Subject: [PATCH 226/383] fix: add time warning --- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 697732412..dc1aa23cf 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -204,7 +204,7 @@ func (d *Distribution) Exec() error { } } - logrus.Info("Deleting blocking resources...") + logrus.Warn("Deleting blocking resources, this operation will take a few minutes!") if err = d.deleteBlockingResources(); err != nil { return err From 38cb2a694397087e70b8ed4d2940996d64a4a7f8 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 13 Apr 2023 15:06:11 +0200 Subject: [PATCH 227/383] fix: don't ask VPN connection if cluster is public or public/private --- go.sum | 2 -- internal/apis/kfd/v1alpha2/eks/creator.go | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.sum b/go.sum index 9b74cb020..f0dc2ee94 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,6 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797 h1:s6lZw+WzoqkAfOOKMeWaCH2P2y/vi7t7jG5FvG0crV8= -github.com/sighupio/fury-distribution v1.25.2-0.20230406151648-ff23f8c4f797/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44 h1:gZ+DBd2tzTUvte7acG368W/6N4xZ0fC4RH0SY8GHGqE= github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 5d995e433..62f06e137 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -162,7 +162,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -198,7 +198,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseDistribution: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -261,7 +261,7 @@ func (v *ClusterCreator) allPhases( } } - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && !v.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 579e0147f32cb5c428cdad284384cb94233adad4 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 13 Apr 2023 15:16:34 +0200 Subject: [PATCH 228/383] fix: linting --- internal/apis/kfd/v1alpha2/eks/creator.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 62f06e137..ff5a0fe81 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -162,7 +162,9 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseKubernetes: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && + !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -198,7 +200,9 @@ func (v *ClusterCreator) Create(skipPhase string) error { return nil case cluster.OperationPhaseDistribution: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && + !v.dryRun { if err = vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } @@ -261,7 +265,9 @@ func (v *ClusterCreator) allPhases( } } - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && !v.dryRun { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && + !v.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 117bb414820fa400497f71c8377af3e8c75d3681 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 13 Apr 2023 16:13:19 +0200 Subject: [PATCH 229/383] fix: linting --- internal/x/slices/clean.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/x/slices/clean.go b/internal/x/slices/clean.go index 607087450..c7ee73b45 100644 --- a/internal/x/slices/clean.go +++ b/internal/x/slices/clean.go @@ -8,7 +8,7 @@ package slices func Clean[T comparable](slice []T) []T { result := make([]T, 0) - zeroValue := *new(T) + var zeroValue T for _, v := range slice { if v != zeroValue { From 6eae671ce52b6b8c83e0138fa0ea6450052797d2 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 13 Apr 2023 16:02:09 +0200 Subject: [PATCH 230/383] feat(distribution): remove blocking deployment explicitely during eks cluster deletion --- .../apis/kfd/v1alpha2/eks/delete/distribution.go | 13 +++++++++++++ internal/kubernetes/client.go | 9 +++++++++ internal/kubernetes/client_test.go | 16 ++++++++++++++++ internal/tool/kubectl/runner.go | 16 +++++++--------- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index dc1aa23cf..abe5bb7f6 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -339,6 +339,18 @@ func (d *Distribution) deleteBlockingResources() error { return fmt.Errorf("error deleting logging resources: %w", err) } + logrus.Info("Deleting Deployments in the namespace 'logging'...") + + _, err = d.kubeClient.DeleteResource("loki-distributed-distributor", "deployment", "logging") + if err != nil { + return fmt.Errorf("error deleting deployment 'loki-distributed-distributor' in logging namespace: %w", err) + } + + _, err = d.kubeClient.DeleteResource("loki-distributed-compactor", "deployment", "logging") + if err != nil { + return fmt.Errorf("error deleting deployment 'loki-distributed-compactor' in logging namespace: %w", err) + } + logrus.Info("Deleting StafultSets in the namespace 'logging'...") _, err = d.kubeClient.DeleteAllResources("sts", "logging") @@ -361,6 +373,7 @@ func (d *Distribution) deleteBlockingResources() error { } logrus.Debugf("waiting for resources to be deleted...") + time.Sleep(dur) return nil diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 341133b0a..5b11cbe4a 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -148,6 +148,15 @@ func (c *Client) DeleteAllResources(res, ns string) (string, error) { return cmdOut, nil } +func (c *Client) DeleteResource(name, res, ns string) (string, error) { + cmdOut, err := c.kubeRunner.DeleteResource(ns, res, name) + if err != nil { + return cmdOut, fmt.Errorf("error while deleting resource from cluster: %w", err) + } + + return cmdOut, nil +} + func (c *Client) DeleteFromPath(path string, params ...string) (string, error) { cmdOut, err := c.kubeRunner.Delete(path, params...) if err != nil { diff --git a/internal/kubernetes/client_test.go b/internal/kubernetes/client_test.go index 63f5273ac..4e407136e 100644 --- a/internal/kubernetes/client_test.go +++ b/internal/kubernetes/client_test.go @@ -107,6 +107,22 @@ func TestClient_DeleteAllResources(t *testing.T) { } } +func TestClient_DeleteResource(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + out, err := client.DeleteResource("res-1", "pod", "default") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + wantOut := `res "res-1" deleted` + if out != wantOut { + t.Errorf("expected output to be '%s', got: '%s'", wantOut, out) + } +} + func TestClient_DeleteFromPath(t *testing.T) { t.Parallel() diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index dd431d997..97887a2e5 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -99,14 +99,8 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { return out, nil } -func (r *Runner) DeleteAllResources(ns, res string) (string, error) { - args := []string{"delete", res, "--all"} - - if ns != "all" { - args = append(args, "-n", ns) - } else { - args = append(args, "-A") - } +func (r *Runner) DeleteResource(ns, res, name string) (string, error) { + args := []string{"delete", res, "-n", ns, name} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -118,12 +112,16 @@ func (r *Runner) DeleteAllResources(ns, res string) (string, error) { WorkDir: r.paths.WorkDir, })) if err != nil { - return out, fmt.Errorf("error deleting all resources: %w", err) + return out, fmt.Errorf("error deleting resource(s): %w", err) } return out, nil } +func (r *Runner) DeleteAllResources(ns, res string) (string, error) { + return r.DeleteResource(ns, res, "--all") +} + func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { args := []string{"delete"} From f682f08318a126ec3c7d184ae22e3a0bb88a6f34 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 16:24:55 +0200 Subject: [PATCH 231/383] feat: removed openvpn requirement when not using --vpn-auto-connect --- cmd/create/cluster.go | 5 +++-- cmd/delete/cluster.go | 5 +++-- cmd/validate/dependencies.go | 2 +- .../apis/kfd/v1alpha2/eks/create/infrastructure.go | 10 ---------- internal/apis/kfd/v1alpha2/eks/tool.go | 11 +++++++---- internal/apis/tool.go | 9 +++++++-- internal/dependencies/tools/validator.go | 6 ++++-- internal/dependencies/tools/validator_test.go | 2 +- internal/dependencies/validate.go | 4 ++-- 9 files changed, 28 insertions(+), 26 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 7455b0871..d8cc687e2 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -139,7 +139,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Init second half of collaborators. depsdl := dependencies.NewDownloader(client, basePath, flags.BinPath) - depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath) + depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) // Validate the furyctl.yaml file. logrus.Info("Validating configuration file...") @@ -388,7 +388,8 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { cmd.Flags().Bool( "vpn-auto-connect", false, - "When set will automatically connect to the created VPN in the infrastructure phase", + "When set will automatically connect to the created VPN in the infrastructure phase, "+ + "please note that this will require OpenVPN to be installed in your system", ) cmd.Flags().Bool( diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index dafd88d29..ac13154e3 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -123,7 +123,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) // Init second half of collaborators. - depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath) + depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) // Validate the dependencies. logrus.Info("Validating dependencies...") @@ -232,7 +232,8 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { cmd.Flags().Bool( "vpn-auto-connect", false, - "When set will automatically connect to the created VPN in the infrastructure phase", + "When set will automatically connect to the created VPN in the infrastructure phase, "+ + "please note that this will require OpenVPN to be installed in your system", ) cmd.Flags().Bool( diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index a017de842..3758ccdd9 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -71,7 +71,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { binPath = filepath.Join(homeDir, ".furyctl", "bin") } - toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath, furyctlPath) + toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath, furyctlPath, false) envVarsValidator := envvars.NewValidator() diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index d3d7b86e6..f1216c84d 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -21,7 +21,6 @@ import ( "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" - "github.com/sighupio/furyctl/internal/tool/openvpn" "github.com/sighupio/furyctl/internal/tool/terraform" bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -41,7 +40,6 @@ type Infrastructure struct { furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD tfRunner *terraform.Runner - ovRunner *openvpn.Runner dryRun bool } @@ -74,10 +72,6 @@ func NewInfrastructure( Terraform: phase.TerraformPath, }, ), - ovRunner: openvpn.NewRunner(executor, openvpn.Paths{ - WorkDir: phase.SecretsPath, - Openvpn: "openvpn", - }), dryRun: dryRun, }, nil } @@ -101,10 +95,6 @@ func (i *Infrastructure) Exec() error { return fmt.Errorf("error creating infrastructure folder structure: %w", err) } - if _, err := i.ovRunner.Version(); err != nil { - return fmt.Errorf("can't get tool version: %w", err) - } - if err := i.createTfVars(); err != nil { return err } diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index 8116e0d39..cfeb16110 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -16,12 +16,14 @@ import ( var ErrOpenVPNNotInstalled = fmt.Errorf("openvpn is not installed") type ExtraToolsValidator struct { - executor execx.Executor + executor execx.Executor + autoConnect bool } -func NewExtraToolsValidator(executor execx.Executor) *ExtraToolsValidator { +func NewExtraToolsValidator(executor execx.Executor, autoConnect bool) *ExtraToolsValidator { return &ExtraToolsValidator{ - executor: executor, + executor: executor, + autoConnect: autoConnect, } } @@ -47,7 +49,8 @@ func (x *ExtraToolsValidator) Validate(confPath string) ([]string, []error) { func (x *ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { if conf.Spec.Infrastructure != nil && - conf.Spec.Infrastructure.Vpn != nil { + conf.Spec.Infrastructure.Vpn != nil && + x.autoConnect { oRunner := openvpn.NewRunner(x.executor, openvpn.Paths{ Openvpn: "openvpn", }) diff --git a/internal/apis/tool.go b/internal/apis/tool.go index d7f380ea7..c61dc1f33 100644 --- a/internal/apis/tool.go +++ b/internal/apis/tool.go @@ -13,12 +13,17 @@ type ExtraToolsValidator interface { Validate(confPath string) ([]string, []error) } -func NewExtraToolsValidatorFactory(executor execx.Executor, apiVersion, kind string) ExtraToolsValidator { +func NewExtraToolsValidatorFactory( + executor execx.Executor, + apiVersion, + kind string, + autoConnect bool, +) ExtraToolsValidator { switch apiVersion { case "kfd.sighup.io/v1alpha2": switch kind { case "EKSCluster": - return eks.NewExtraToolsValidator(executor) + return eks.NewExtraToolsValidator(executor, autoConnect) default: return nil diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index 2433fe8ba..d56862bd9 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -20,13 +20,14 @@ var ( ErrWrongToolVersion = errors.New("wrong tool version") ) -func NewValidator(executor execx.Executor, binPath, furyctlPath string) *Validator { +func NewValidator(executor execx.Executor, binPath, furyctlPath string, autoConnect bool) *Validator { return &Validator{ executor: executor, toolFactory: NewFactory(executor, FactoryPaths{ Bin: binPath, }), furyctlPath: furyctlPath, + autoConnect: autoConnect, } } @@ -34,6 +35,7 @@ type Validator struct { executor execx.Executor toolFactory *Factory furyctlPath string + autoConnect bool } func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ([]string, []error) { @@ -71,7 +73,7 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( } } - etv := apis.NewExtraToolsValidatorFactory(tv.executor, miniConf.APIVersion, miniConf.Kind) + etv := apis.NewExtraToolsValidatorFactory(tv.executor, miniConf.APIVersion, miniConf.Kind, tv.autoConnect) if etv == nil { return oks, errs diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 796f8deaa..b8d7e2a93 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -134,7 +134,7 @@ func Test_Validator_Validate(t *testing.T) { t.Run(tC.desc, func(t *testing.T) { furyctlPath := path.Join("test_data", "furyctl.yaml") - v := tools.NewValidator(execx.NewFakeExecutor(), "test_data", furyctlPath) + v := tools.NewValidator(execx.NewFakeExecutor(), "test_data", furyctlPath, false) oks, errs := v.Validate(tC.manifest, tC.state) diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index 7a309c08b..0c1f07a26 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -21,9 +21,9 @@ var ( errValidatingToolsConf = errors.New("errors validating tools configuration") ) -func NewValidator(executor execx.Executor, binPath, furyctlPath string) *Validator { +func NewValidator(executor execx.Executor, binPath, furyctlPath string, autoConnect bool) *Validator { return &Validator{ - toolsValidator: tools.NewValidator(executor, binPath, furyctlPath), + toolsValidator: tools.NewValidator(executor, binPath, furyctlPath, autoConnect), envVarsValidator: envvars.NewValidator(), infraValidator: toolsconf.NewValidator(executor), } From 494d80a7575755a2fac8d0b0bbb351bf7039cb79 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 16:25:56 +0200 Subject: [PATCH 232/383] chore: upped fury distribution in go.mod --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 757b24d79..844d6fb0f 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44 + github.com/sighupio/fury-distribution v1.25.2-0.20230414123658-a2f7f6b6ce42 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index f0dc2ee94..c1874b58f 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44 h1:gZ+DBd2tzTUvte7acG368W/6N4xZ0fC4RH0SY8GHGqE= github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230414123658-a2f7f6b6ce42 h1:OpAJM3FD9TdN/e7I/6oZVBMasm2suU5EDJYI6/f3ekQ= +github.com/sighupio/fury-distribution v1.25.2-0.20230414123658-a2f7f6b6ce42/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 9cbbc9bb4f4852f6fb1c56a8e7a3acc17cb6cc24 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 16:40:18 +0200 Subject: [PATCH 233/383] chore: stronger check on vpn --- internal/apis/kfd/v1alpha2/eks/tool.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index cfeb16110..3e7c3188d 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -49,7 +49,9 @@ func (x *ExtraToolsValidator) Validate(confPath string) ([]string, []error) { func (x *ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { if conf.Spec.Infrastructure != nil && - conf.Spec.Infrastructure.Vpn != nil && + conf.Spec.Infrastructure.Vpn != nil && (conf.Spec.Infrastructure.Vpn.Instances == nil || + conf.Spec.Infrastructure.Vpn.Instances != nil && + *conf.Spec.Infrastructure.Vpn.Instances > 0) && x.autoConnect { oRunner := openvpn.NewRunner(x.executor, openvpn.Paths{ Openvpn: "openvpn", From 526f4a79033bf00ef42495d51fd5b4c0f755eec9 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 17:10:45 +0200 Subject: [PATCH 234/383] chore: better groups in condition --- internal/apis/kfd/v1alpha2/eks/tool.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index 3e7c3188d..a1ab19791 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -49,9 +49,10 @@ func (x *ExtraToolsValidator) Validate(confPath string) ([]string, []error) { func (x *ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error { if conf.Spec.Infrastructure != nil && - conf.Spec.Infrastructure.Vpn != nil && (conf.Spec.Infrastructure.Vpn.Instances == nil || - conf.Spec.Infrastructure.Vpn.Instances != nil && - *conf.Spec.Infrastructure.Vpn.Instances > 0) && + conf.Spec.Infrastructure.Vpn != nil && + (conf.Spec.Infrastructure.Vpn.Instances == nil || + (conf.Spec.Infrastructure.Vpn.Instances != nil && + *conf.Spec.Infrastructure.Vpn.Instances > 0)) && x.autoConnect { oRunner := openvpn.NewRunner(x.executor, openvpn.Paths{ Openvpn: "openvpn", From 3c94bbc1b6f0fece9143617988ec007520444941 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 14 Apr 2023 18:20:36 +0200 Subject: [PATCH 235/383] fix: configure code coverage correctly for tests --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 66221753d..5d46f89f3 100644 --- a/Makefile +++ b/Makefile @@ -147,11 +147,11 @@ test-expensive: @GOFLAGS=-mod=mod ginkgo run -vv --trace -tags=expensive -timeout 36000s -p test/expensive test-most: - @GOFLAGS=-mod=mod ginkgo run -vv --trace -covermode=count -coverprofile=coverage.out -tags=unit,integration,e2e,expensive --skip-package=expensive -timeout 300s -p ./... + @GOFLAGS=-mod=mod ginkgo run -vv --trace -coverpkg=./... -covermode=count -coverprofile=coverage.out -tags=unit,integration,e2e,expensive --skip-package=expensive -timeout 300s -p ./... test-all: $(call yes-or-no, "WARNING: This test will create a cluster on AWS. Are you sure you want to continue?") - @GOFLAGS=-mod=mod ginkgo run -vv --trace -covermode=count -coverprofile=coverage.out -tags=unit,integration,e2e,expensive -timeout 300s -p ./... + @GOFLAGS=-mod=mod ginkgo run -vv --trace -coverpkg=./... -covermode=count -coverprofile=coverage.out -tags=unit,integration,e2e,expensive -timeout 300s -p ./... show-coverage: @go tool cover -html=coverage.out -o coverage.html From 2847810f177cd2ea7471fb60f6cc0e21b6471aab Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 14 Apr 2023 18:19:31 +0200 Subject: [PATCH 236/383] feat: add existence check on single resources before deleting them --- .../kfd/v1alpha2/eks/delete/distribution.go | 22 +++++++++++++++---- internal/kubernetes/client.go | 11 ++++++++++ internal/tool/kubectl/runner.go | 19 ++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index abe5bb7f6..5ec16b2e4 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -341,14 +341,28 @@ func (d *Distribution) deleteBlockingResources() error { logrus.Info("Deleting Deployments in the namespace 'logging'...") - _, err = d.kubeClient.DeleteResource("loki-distributed-distributor", "deployment", "logging") + resExists, err := d.kubeClient.ResourceExists("loki-distributed-distributor", "deployment", "logging") if err != nil { - return fmt.Errorf("error deleting deployment 'loki-distributed-distributor' in logging namespace: %w", err) + return fmt.Errorf("error checking if resource 'loki-distributed-distributor' exists in logging namespace: %w", err) } - _, err = d.kubeClient.DeleteResource("loki-distributed-compactor", "deployment", "logging") + if resExists { + _, err = d.kubeClient.DeleteResource("loki-distributed-distributor", "deployment", "logging") + if err != nil { + return fmt.Errorf("error deleting deployment 'loki-distributed-distributor' in logging namespace: %w", err) + } + } + + resExists, err = d.kubeClient.ResourceExists("loki-distributed-compactor", "deployment", "logging") if err != nil { - return fmt.Errorf("error deleting deployment 'loki-distributed-compactor' in logging namespace: %w", err) + return fmt.Errorf("error checking if resource 'loki-distributed-distributor' exists in logging namespace: %w", err) + } + + if resExists { + _, err = d.kubeClient.DeleteResource("loki-distributed-compactor", "deployment", "logging") + if err != nil { + return fmt.Errorf("error deleting deployment 'loki-distributed-compactor' in logging namespace: %w", err) + } } logrus.Info("Deleting StafultSets in the namespace 'logging'...") diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 5b11cbe4a..3a0b6fb4d 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -148,6 +148,17 @@ func (c *Client) DeleteAllResources(res, ns string) (string, error) { return cmdOut, nil } +func (c *Client) ResourceExists(name, res, ns string) (bool, error) { + cmdOut, err := c.kubeRunner.Get(ns, res, name, "-o", "jsonpath='{.metadata.name}'") + if err != nil { + return false, fmt.Errorf("error while reading resources from cluster: %w", err) + } + + idx := cmdOutRegex.FindStringIndex(cmdOut) + + return idx != nil, nil +} + func (c *Client) DeleteResource(name, res, ns string) (string, error) { cmdOut, err := c.kubeRunner.DeleteResource(ns, res, name) if err != nil { diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 97887a2e5..c4a6aaf61 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -99,6 +99,25 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { return out, nil } +func (r *Runner) GetResource(ns, res, name string) (string, error) { + args := []string{"get", res, "-n", ns, name} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return out, fmt.Errorf("error while getting resources: %w", err) + } + + return out, nil +} + func (r *Runner) DeleteResource(ns, res, name string) (string, error) { args := []string{"delete", res, "-n", ns, name} From 9332fb9a6ebaa406986eabfd32b15832c3e5224f Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 14 Apr 2023 18:52:49 +0200 Subject: [PATCH 237/383] chore: bump fury-distribution dep --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 844d6fb0f..ca96841c9 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.2-0.20230414123658-a2f7f6b6ce42 + github.com/sighupio/fury-distribution v1.25.2-0.20230414160033-2b07e27145d8 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index c1874b58f..8b66fedee 100644 --- a/go.sum +++ b/go.sum @@ -298,10 +298,8 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44 h1:gZ+DBd2tzTUvte7acG368W/6N4xZ0fC4RH0SY8GHGqE= -github.com/sighupio/fury-distribution v1.25.2-0.20230412133444-79d571139d44/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.2-0.20230414123658-a2f7f6b6ce42 h1:OpAJM3FD9TdN/e7I/6oZVBMasm2suU5EDJYI6/f3ekQ= -github.com/sighupio/fury-distribution v1.25.2-0.20230414123658-a2f7f6b6ce42/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.2-0.20230414160033-2b07e27145d8 h1:jHEi85IFEA5AQPYnPPPoTnjxfsq3K20c4p4bw5Kwenk= +github.com/sighupio/fury-distribution v1.25.2-0.20230414160033-2b07e27145d8/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 7baa381e445fe6d02b93a347a228688b23812f99 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 14 Apr 2023 19:47:40 +0200 Subject: [PATCH 238/383] fix: add kubectl get flag that allows for existance check without errors --- internal/kubernetes/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 3a0b6fb4d..fd066e170 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -149,7 +149,7 @@ func (c *Client) DeleteAllResources(res, ns string) (string, error) { } func (c *Client) ResourceExists(name, res, ns string) (bool, error) { - cmdOut, err := c.kubeRunner.Get(ns, res, name, "-o", "jsonpath='{.metadata.name}'") + cmdOut, err := c.kubeRunner.Get(ns, res, name, "--ignore-not-found", "true", "-o", "jsonpath='{.metadata.name}'") if err != nil { return false, fmt.Errorf("error while reading resources from cluster: %w", err) } From 3de630c77d41a63d62f70487852bd3cf1743a270 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 17 Apr 2023 13:08:40 +0200 Subject: [PATCH 239/383] chore: update docs --- README.md | 140 ++++++++++++++++++++++++++++-------------- examples/furyctl.yaml | 102 ++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 45 deletions(-) create mode 100644 examples/furyctl.yaml diff --git a/README.md b/README.md index 671bcff46..8c7452de8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ [![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg?ref=refs/heads/furyctl-ng-alpha1)](https://ci.sighup.io/sighupio/furyctl) -![Release](https://img.shields.io/badge/furyctl%20Next%20Generation-alpha1-blue) +![Release](https://img.shields.io/badge/furyctl-v0.10.0-blue) +![Release](https://img.shields.io/badge/furyctl%20next-v0.25.0‐alpha.1-blue) ![Slack](https://img.shields.io/badge/slack-@kubernetes/fury-yellow.svg?logo=slack) ![License](https://img.shields.io/github/license/sighupio/furyctl) [![Go Report Card](https://goreportcard.com/badge/github.com/sighupio/furyctl)](https://goreportcard.com/report/github.com/sighupio/furyctl) @@ -19,6 +20,8 @@ +> We are in the process of rewriting `furyctl` from the ground up. The new version is called `furyctl-ng` and is currently in `alpha` status, and will be released starting from version `v0.25.0-alpha.1`. The former version of `furyctl` will be enter 'bugfix only' maintenance until the new version is stable enough to replace it, and it will live under the old `v0.1x` branches. + `furyctl` is the command line companion for the Kubernetes Fury Distribution to manage the **full lifecycle** of your Kubernetes Fury clusters.
@@ -40,21 +43,66 @@ ## Installation -### Installation from source +### Installing from binaries + +You can find `furyctl` binaries on the [Releases page](https://github.com/sighupio/furyctl/releases). + +To download the latest release, run: + +```console +wget -q "https://github.com/sighupio/furyctl/releases/download/v0.25.0-alpha.1/furyctl-$(uname -s)-amd64" -O /tmp/furyctl +chmod +x /tmp/furyctl +sudo mv /tmp/furyctl /usr/local/bin/furyctl +``` + +Alternatively, you can install `furyctl` using a brew tap or via an asdf plugin. + +> ❗️**WARNING** +> +> M1 users: please download `darwin/amd64` binaries instead of using homebrew or asdf. Even though furyctl can be built for `arm64`, some of its dependendecies are not available yet for this architecture. + + + +### Installing from source Prerequisites: -- `make` -- `go == v1.19` -- `goreleaser == v1.11.4` +- `make >= 4.1` +- `go >= 1.19` +- `goreleaser >= v1.15` > You can install `goreleaser` with the following command once you have Go in your system: > > ```console -> go install github.com/goreleaser/goreleaser@v1.11.4 +> go install github.com/goreleaser/goreleaser@v1.15.2 > ``` -To install `furyctl` from source, follow the next steps: +Once you've ensured the above dependencies are installed, you can proceed with the installation. 1. Clone the repository: @@ -149,8 +197,7 @@ See all the available commands and their usage by running `furyctl help`. -> **Warning** -> (furyctl-ng alpha version only) +> ❗️**WARNING** > > `furyctl-ng` is compatible with KFD versions 1.22.1, 1.23.3 and 1.24.0, but you will need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` > in _every command_ until the next release of the KFD. @@ -167,16 +214,18 @@ Basic usage of `furyctl` for a new project consists on the following steps: `furyctl` provides a command that outputs a sample configuration file (by default called `furyctl.yaml`) with all the possible fields explained in comments. -furyctl configuration files have a kind, that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a KFD cluster using the EKS managed clusters from AWS. +Furyctl configuration files have a kind that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a KFD cluster using the EKS managed clusters from AWS. Additionaly, the schema of the file is versioned with the `apiVersion` field, so when new features are introduced you can switch to a newer version of the configuration file structure. -To create a sample configuration file as a starting point use the following command: +To scaffold a configuration file to use as a starter, you use the following command: ```console furyctl create config --version ``` +Alternatively, you can take a look at the one in the [examples folder](./examples/). + > 💡 **TIP** > > You can pass some additional flags, like the schema (API) version of the configuration file or a different configuration file name. @@ -188,10 +237,12 @@ Open the generated configuration file with your editor of choice and edit it acc Once you have filled your configuration file, you can check that it's content is valid by running the following comand: ```console -furyctl validate config --config --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' +furyctl validate config --config /path/to/your/furyctl.yaml ``` -> **Note** the `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` +> 📖 **NOTE** +> +> The `--config` flag is optional, set it if your configuration file is not named `furyctl.yaml` #### 2. Create a cluster @@ -202,23 +253,25 @@ Requirements (EKSCluster): In the previous step, you have created and validated a configuration file that defines the Kubernetes cluster and its sorroundings, you can now proceed to actually creating the resources. -furyctl has divided the cluster creation in three phases: `infrastructure`, `kubernetes` and `distribution`. +Furyctl divides the cluster creation in three phases: `infrastructure`, `kubernetes` and `distribution`. 1. The first phase, `infrastructure`, creates all the prerequisites needed to be able to create a cluster. For example, the VPC and its networks. 2. The second phase, `kubernetes`, creates the actual Kubernetes clusters. For example, the EKS cluster and its node pools. 3. The third phase, `distribution`, deploys KFD modules to the Kubernetes cluster. -> 💡 You may find these phases familiar from editing the configuration file. +> 📖 **NOTE** +> +> You will find these three phases when editing the furyctl.yaml file. -Just like you can validate that your configuration file is well formed, `furyctl` let's you check that you have all the needed dependencies (environment variables, binaries, etc.) before starting a cluster creation process. +Just like you can validate that your configuration file is well formed, `furyctl` let you check that you have all the needed dependencies (environment variables, binaries, etc.) before starting a cluster creation process. To validate that your system has all the dependencies needed to create the cluster defined in your configuration file, run the following command: ```console -furyctl validate dependencies --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' +furyctl validate dependencies ``` -Finally, to launch the creation of the resources defined in the configuration file, run the following command: +Last but not least, you can launch the creation of the resources defined in the configuration file by running the following command: > **:warning:** you are about to create cloud resources that could have billing impact. @@ -227,12 +280,14 @@ Finally, to launch the creation of the resources defined in the configuration fi > **:info:** the creation process you are about to launch can take a while. ```console -furyctl create cluster --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' +furyctl create cluster --config /path/to/your/furyctl.yaml ``` -> **Note** the creation process can take a while. +> 📖 **NOTE** +> +> The creation process will take a while. -🎉 Congratulations! You have created your production-grade Kubernetes Fury Cluster from scratch and it's ready to go into battle. +🎉 Congratulations! You have created your production-grade Kubernetes Fury Cluster from scratch and it is now ready to go into battle. #### 3. Destroy a cluster @@ -240,13 +295,15 @@ Destroying a cluster can be thought as running the creation phases in reverse or To destroy a cluster created using `furyctl` and all its related resources, run the following command: -> **Warning** you are about to run a destructive operation. +> ❗️ **WARNING** +> +> You are about to run a destructive operation. ```console -furyctl delete cluster --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' --dry-run +furyctl delete cluster --dry-run ``` -check that the dry-run output is what you expected and then run the command again without the `--dry-run` flag to actually delete all the resources. +Check that the dry-run output is what you expect and then run the command again without the `--dry-run` flag to actually delete all the resources. > 💡 **TIP** > @@ -254,7 +311,7 @@ check that the dry-run output is what you expected and then run the command agai ### Advanced Usage -#### Download and manage KFD modules +#### KFD modules management `furyctl` can be used as a package manager for KFD. @@ -262,25 +319,18 @@ It provides a simple way to download all the desired modules of the KFD by readi The process requires the following steps: -1. Generate a `furyctl.yaml` by running `furyctl create config` specifying the desired Kubernetes Fury Distribution version - with the flag `--version`. +1. Generate a `furyctl.yaml` by running `furyctl create config` specifying the desired Kubernetes Fury Distribution version using the `--version` flag. 2. Run `furyctl download dependencies` to download all the dependencies including the modules of the KFD. -##### 1. Customize the `furyctl.yaml` +##### 1. Customizing the `furyctl.yaml` -A `furyctl.yaml` is a YAML formatted file that contains all the information needed to create a Kubernetes Fury cluster. +A `furyctl.yaml` is a YAML-formatted file that contains all the information that is needed to create a Kubernetes Fury cluster. Modules are located in the `distribution` section of the `furyctl.yaml` file and can be configured to better fit your needs. -##### 2. Download the modules +##### 2. Downloading the modules -Run `furyctl download dependencies` (within the same directory where your `furyctl.yaml` is located) to download the modules and all the dependencies -needed to create a Kubernetes Fury cluster. - -> 🔥 **Advanced Tip** -> -> Using the command `furyctl dump template` with the flag `--distro-location` pointing to the local location of the repository `fury-distribution`, -> will run the template engine on the modules and generate the final manifests that will be applied to the cluster. +Run `furyctl download dependencies` (within the same directory where your `furyctl.yaml` is located) to download the modules and all the dependencies that are needed to create a Kubernetes Fury cluster. #### Cluster creation @@ -316,28 +366,28 @@ but you can limit the execution to a specific phase by using the flag `--phase`. To create a cluster step by step, you can run the following command: ```bash -furyctl create cluster --phase infrastructure --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' +furyctl create cluster --phase infrastructure' ``` If you choose to create a VPN in the infrastructure phase, you can automatically connect to it by using the flag `--vpn-auto-connect`. ```bash -furyctl create cluster --phase kubernetes --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' +furyctl create cluster --phase kubernetes' ``` After running the command, remember to export the `KUBECONFIG` environment variable to point to the generated kubeconfig file or to use the flag `--kubeconfig` in the following command. ```bash -furyctl create cluster --phase distribution --distro-location 'git::git@github.com:sighupio/fury-distribution.git?depth=1&ref=feature/furyctl-next' +furyctl create cluster --phase distribution' ``` - - +### Advanced Tips -## Contributing +Furyctl comes with the flag `--distro-location`, allowing you to use a local copy of KFD instead of downloading it from the internet. This allows you to test changes to the KFD without having to push them to the repository, and might come in handy when you need to test new features or bugfixes. -Before contributing, please read first the [Contributing Guidelines](docs/CONTRIBUTING.md). + + ### Test classes @@ -357,7 +407,7 @@ That said, here's a little summary of the used tags: ### Reporting Issues -In case you experience any problems with `furyctl`, please [open a new issue](https://github.com/sighupio/furyctl/issues/new/choose) in GitHub. +In case you experience any problems with `furyctl`, please [open a new issue](https://github.com/sighupio/furyctl/issues/new/choose) on GitHub. ## License diff --git a/examples/furyctl.yaml b/examples/furyctl.yaml new file mode 100644 index 000000000..7264488c4 --- /dev/null +++ b/examples/furyctl.yaml @@ -0,0 +1,102 @@ + +--- +apiVersion: kfd.sighup.io/v1alpha2 +kind: EKSCluster +metadata: + name: example +spec: + distributionVersion: v1.25.2 + toolsConfiguration: + terraform: + state: + s3: + bucketName: example + keyPrefix: test/example + region: eu-west-1 + region: eu-west-1 + tags: + env: "test" + k8s: "example" + githubOrg: "acme" + githubRepo: "furyctl" + infrastructure: + vpc: + network: + cidr: 10.10.0.0/16 + subnetsCidrs: + private: + - 10.10.0.0/20 + - 10.10.16.0/20 + - 10.10.32.0/20 + - 10.10.48.0/20 + public: + - 10.10.192.0/24 + - 10.10.193.0/24 + - 10.10.194.0/24 + vpn: + instances: 1 + instanceType: t3.micro + diskSize: 50 + operatorName: acme + dhParamsBits: 2048 + vpnClientsSubnetCidr: 172.16.0.0/16 + ssh: + publicKeys: ["{file:////.ssh/id_rsa.pub}"] + githubUsersName: + - + allowedFromCidrs: + - 0.0.0.0/0 + kubernetes: + apiServer: + privateAccess: true + publicAccess: false + privateAccessCidrs: ["0.0.0.0/0"] + publicAccessCidrs: [] + nodeAllowedSshPublicKey: "{file:////.ssh/id_rsa.pub}" + nodePoolsLaunchKind: "launch_templates" + logRetentionDays: 1 + nodePools: + - name: infra + size: + min: 3 + max: 3 + instance: + type: t3.xlarge + spot: true + volumeSize: 50 + labels: + nodepool: infra + node.kubernetes.io/role: infra + taints: + - node.kubernetes.io/role=infra:NoSchedule + tags: + k8s.io/cluster-autoscaler/node-template/label/nodepool: "infra" + k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "infra" + k8s.io/cluster-autoscaler/node-template/taint/node.kubernetes.io/role: "infra:NoSchedule" + distribution: + modules: + ingress: + baseDomain: internal.example.dev + nginx: + type: dual + tls: + provider: certManager + certManager: + clusterIssuer: + name: letsencrypt-fury + email: fury@example.dev + type: http01 + dns: + public: + name: "example.dev" + create: true + private: + name: "internal.example.dev" + create: true + logging: + type: loki + dr: + velero: + eks: + bucketName: furyctl-example-qgcurpen + region: eu-west-1 From c1c4a452b891d779b7c4b07c1dcc5b448fac9065 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 17 Apr 2023 16:09:49 +0200 Subject: [PATCH 240/383] fix: delete resources with ns=all --- internal/tool/kubectl/runner.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index c4a6aaf61..7e565fb0f 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -119,7 +119,13 @@ func (r *Runner) GetResource(ns, res, name string) (string, error) { } func (r *Runner) DeleteResource(ns, res, name string) (string, error) { - args := []string{"delete", res, "-n", ns, name} + args := []string{"delete", res} + + if ns != "all" { + args = append(args, "-n", ns) + } + + args = append(args, name) if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) From 66b90aedffaeef4c6332b27d7f7fe080248d64ea Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 17 Apr 2023 17:18:22 +0200 Subject: [PATCH 241/383] feat: split delete functions --- .../kfd/v1alpha2/eks/delete/distribution.go | 14 ++-- internal/kubernetes/client.go | 16 ++++- internal/kubernetes/client_test.go | 20 +++++- internal/tool/kubectl/runner.go | 65 +++++++++++++++++-- 4 files changed, 97 insertions(+), 18 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 5ec16b2e4..44d07c732 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -307,7 +307,7 @@ func (d *Distribution) checkPendingResources() error { } func (d *Distribution) deleteIngresses() error { - _, err := d.kubeClient.DeleteAllResources("ingress", "all") + _, err := d.kubeClient.DeleteResourcesInAllNamespaces("ingress") if err != nil { return fmt.Errorf("error deleting ingresses: %w", err) } @@ -320,21 +320,21 @@ func (d *Distribution) deleteBlockingResources() error { logrus.Info("Deleting prometheus resources...") - _, err := d.kubeClient.DeleteAllResources("prometheus", "monitoring") + _, err := d.kubeClient.DeleteResources("prometheus", "monitoring") if err != nil { return fmt.Errorf("error deleting prometheus resources: %w", err) } logrus.Info("Deleting PersistentVolumeClaims in the namespace 'monitoring'...") - _, err = d.kubeClient.DeleteAllResources("pvc", "monitoring") + _, err = d.kubeClient.DeleteResources("pvc", "monitoring") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'monitoring': %w", err) } logrus.Info("Deleting logging resources...") - _, err = d.kubeClient.DeleteAllResources("logging", "logging") + _, err = d.kubeClient.DeleteResources("logging", "logging") if err != nil { return fmt.Errorf("error deleting logging resources: %w", err) } @@ -367,21 +367,21 @@ func (d *Distribution) deleteBlockingResources() error { logrus.Info("Deleting StafultSets in the namespace 'logging'...") - _, err = d.kubeClient.DeleteAllResources("sts", "logging") + _, err = d.kubeClient.DeleteResources("sts", "logging") if err != nil { return fmt.Errorf("error deleting sts in namespace 'logging': %w", err) } logrus.Info("Deleting PersistentVolumeClaims in the namespace 'logging'...") - _, err = d.kubeClient.DeleteAllResources("pvc", "logging") + _, err = d.kubeClient.DeleteResources("pvc", "logging") if err != nil { return fmt.Errorf("error deleting pvc in namespace 'logging': %w", err) } logrus.Info("Deleting Services in the namespace 'ingress-nginx'...") - _, err = d.kubeClient.DeleteAllResources("svc", "ingress-nginx") + _, err = d.kubeClient.DeleteResources("svc", "ingress-nginx") if err != nil { return fmt.Errorf("error deleting svc in namespace 'ingress-nginx': %w", err) } diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index fd066e170..6e29f5887 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -139,8 +139,19 @@ func (c *Client) GetLoadBalancers() ([]string, error) { return slices.Clean(strings.Split(cmdOut[idx[0]+1:idx[1]-1], " ")), nil } -func (c *Client) DeleteAllResources(res, ns string) (string, error) { - cmdOut, err := c.kubeRunner.DeleteAllResources(ns, res) +// DeleteResources deletes the specified resources in the specified namespace +func (c *Client) DeleteResources(res, ns string) (string, error) { + cmdOut, err := c.kubeRunner.DeleteResources(ns, res) + if err != nil { + return cmdOut, fmt.Errorf("error while deleting resources from cluster: %w", err) + } + + return cmdOut, nil +} + +// DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces +func (c *Client) DeleteResourcesInAllNamespaces(res string) (string, error) { + cmdOut, err := c.kubeRunner.DeleteResourcesInAllNamespaces(res) if err != nil { return cmdOut, fmt.Errorf("error while deleting resources from cluster: %w", err) } @@ -159,6 +170,7 @@ func (c *Client) ResourceExists(name, res, ns string) (bool, error) { return idx != nil, nil } +// DeleteResource deletes the specified resource in the specified namespace func (c *Client) DeleteResource(name, res, ns string) (string, error) { cmdOut, err := c.kubeRunner.DeleteResource(ns, res, name) if err != nil { diff --git a/internal/kubernetes/client_test.go b/internal/kubernetes/client_test.go index 4e407136e..04b65fddd 100644 --- a/internal/kubernetes/client_test.go +++ b/internal/kubernetes/client_test.go @@ -91,12 +91,28 @@ func TestClient_GetLoadBalancers(t *testing.T) { } } -func TestClient_DeleteAllResources(t *testing.T) { +func TestClient_DeleteResourcesInAllNamespaces(t *testing.T) { t.Parallel() client := FakeClient(t) - out, err := client.DeleteAllResources("pod", "default") + out, err := client.DeleteResourcesInAllNamespaces("pod") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + wantOut := `res "res-1" deleted` + if out != wantOut { + t.Errorf("expected output to be '%s', got: '%s'", wantOut, out) + } +} + +func TestClient_DeleteResources(t *testing.T) { + t.Parallel() + + client := FakeClient(t) + + out, err := client.DeleteResources("pod", "default") if err != nil { t.Errorf("expected no error, got %v", err) } diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 7e565fb0f..c0a6962ee 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -118,14 +118,29 @@ func (r *Runner) GetResource(ns, res, name string) (string, error) { return out, nil } +// DeleteResource deletes the specified resource in the specified namespace func (r *Runner) DeleteResource(ns, res, name string) (string, error) { - args := []string{"delete", res} + args := []string{"delete", "--namespace", ns, res, name} - if ns != "all" { - args = append(args, "-n", ns) + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - args = append(args, name) + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return out, fmt.Errorf("error deleting resource(s) \"%s/%s/%s\": %w", ns, res, name, err) + } + + return out, nil +} + +// DeleteResources deletes the specified resources in the specified namespace +func (r *Runner) DeleteResources(ns, res string) (string, error) { + args := []string{"delete", "--namespace", ns, "--all", res} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -137,14 +152,50 @@ func (r *Runner) DeleteResource(ns, res, name string) (string, error) { WorkDir: r.paths.WorkDir, })) if err != nil { - return out, fmt.Errorf("error deleting resource(s): %w", err) + return out, fmt.Errorf("error deleting resource(s) \"%s/%s\": %w", ns, res, err) } return out, nil } -func (r *Runner) DeleteAllResources(ns, res string) (string, error) { - return r.DeleteResource(ns, res, "--all") +// // DeleteAllResources deletes all resources in the specified namepsace +// func (r *Runner) DeleteAllResources(ns string) (string, error) { +// args := []string{"delete", "--namespace", ns, "--all"} + +// if r.paths.Kubeconfig != "" { +// args = append(args, "--kubeconfig", r.paths.Kubeconfig) +// } + +// out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ +// Args: args, +// Executor: r.executor, +// WorkDir: r.paths.WorkDir, +// })) +// if err != nil { +// return out, fmt.Errorf("error deleting all resources in namespace \"%s\": %w", ns, err) +// } + +// return out, nil +// } + +// DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces +func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { + args := []string{"delete", "--all-namespaces", res} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return out, fmt.Errorf("error deleting all \"%s\" resources in all namespaces: %w", res, err) + } + + return out, nil } func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { From fd8027fa20bb847d743aae14c776b2e285eb92b1 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 17 Apr 2023 17:19:11 +0200 Subject: [PATCH 242/383] doc: remove comment --- internal/tool/kubectl/runner.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index c0a6962ee..529070072 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -158,26 +158,6 @@ func (r *Runner) DeleteResources(ns, res string) (string, error) { return out, nil } -// // DeleteAllResources deletes all resources in the specified namepsace -// func (r *Runner) DeleteAllResources(ns string) (string, error) { -// args := []string{"delete", "--namespace", ns, "--all"} - -// if r.paths.Kubeconfig != "" { -// args = append(args, "--kubeconfig", r.paths.Kubeconfig) -// } - -// out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ -// Args: args, -// Executor: r.executor, -// WorkDir: r.paths.WorkDir, -// })) -// if err != nil { -// return out, fmt.Errorf("error deleting all resources in namespace \"%s\": %w", ns, err) -// } - -// return out, nil -// } - // DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { args := []string{"delete", "--all-namespaces", res} From ff363ca745f227ba13c1f08548218ad6fc841e79 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 17 Apr 2023 17:33:29 +0200 Subject: [PATCH 243/383] fix: linting --- internal/kubernetes/client.go | 6 +++--- internal/tool/kubectl/runner.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 6e29f5887..1a9721947 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -139,7 +139,7 @@ func (c *Client) GetLoadBalancers() ([]string, error) { return slices.Clean(strings.Split(cmdOut[idx[0]+1:idx[1]-1], " ")), nil } -// DeleteResources deletes the specified resources in the specified namespace +// DeleteResources deletes the specified resources in the specified namespace. func (c *Client) DeleteResources(res, ns string) (string, error) { cmdOut, err := c.kubeRunner.DeleteResources(ns, res) if err != nil { @@ -149,7 +149,7 @@ func (c *Client) DeleteResources(res, ns string) (string, error) { return cmdOut, nil } -// DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces +// DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces. func (c *Client) DeleteResourcesInAllNamespaces(res string) (string, error) { cmdOut, err := c.kubeRunner.DeleteResourcesInAllNamespaces(res) if err != nil { @@ -170,7 +170,7 @@ func (c *Client) ResourceExists(name, res, ns string) (bool, error) { return idx != nil, nil } -// DeleteResource deletes the specified resource in the specified namespace +// DeleteResource deletes the specified resource in the specified namespace. func (c *Client) DeleteResource(name, res, ns string) (string, error) { cmdOut, err := c.kubeRunner.DeleteResource(ns, res, name) if err != nil { diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 529070072..4b097166f 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -118,7 +118,7 @@ func (r *Runner) GetResource(ns, res, name string) (string, error) { return out, nil } -// DeleteResource deletes the specified resource in the specified namespace +// DeleteResource deletes the specified resource in the specified namespace. func (r *Runner) DeleteResource(ns, res, name string) (string, error) { args := []string{"delete", "--namespace", ns, res, name} @@ -138,7 +138,7 @@ func (r *Runner) DeleteResource(ns, res, name string) (string, error) { return out, nil } -// DeleteResources deletes the specified resources in the specified namespace +// DeleteResources deletes the specified resources in the specified namespace. func (r *Runner) DeleteResources(ns, res string) (string, error) { args := []string{"delete", "--namespace", ns, "--all", res} @@ -158,7 +158,7 @@ func (r *Runner) DeleteResources(ns, res string) (string, error) { return out, nil } -// DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces +// DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces. func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { args := []string{"delete", "--all-namespaces", res} From 4cc816893a381b5d15ebe87040e681241cc6c3c3 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 17 Apr 2023 18:12:31 +0200 Subject: [PATCH 244/383] fix: add license --- examples/furyctl.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/furyctl.yaml b/examples/furyctl.yaml index 7264488c4..4904ede2c 100644 --- a/examples/furyctl.yaml +++ b/examples/furyctl.yaml @@ -1,3 +1,7 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + --- apiVersion: kfd.sighup.io/v1alpha2 From 66b378f198a742597f71d560555ce835f8e64a84 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 15:56:13 +0200 Subject: [PATCH 245/383] feat: ovpn certs are created each time cluster create run --- internal/apis/kfd/v1alpha2/eks/vpn.go | 45 +++++++++++++++++++++++++++ internal/tool/awscli/runner.go | 16 ++++++++++ internal/tool/furyagent/config.go | 27 ++++++++++++++++ internal/tool/furyagent/runner.go | 7 ++++- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 internal/tool/furyagent/config.go diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 4d41e9cbe..645f2a8d4 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -18,6 +18,7 @@ import ( "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/furyagent" "github.com/sighupio/furyctl/internal/tool/openvpn" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -38,6 +39,7 @@ type VpnConnector struct { config *private.SpecInfrastructureVpn ovRunner *openvpn.Runner faRunner *furyagent.Runner + awsRunner *awscli.Runner } func NewVpnConnector( @@ -65,6 +67,13 @@ func NewVpnConnector( Furyagent: path.Join(binPath, "furyagent", faVersion, "furyagent"), WorkDir: certDir, }), + awsRunner: awscli.NewRunner( + execx.NewStdExecutor(), + awscli.Paths{ + Awscli: "aws", + WorkDir: certDir, + }, + ), } } @@ -101,6 +110,17 @@ func (v *VpnConnector) GenerateCertificates() error { return err } + bucketName, err := v.getFuryAgentBucketName() + if err != nil { + return err + } + + if err := v.assertOldCertificateExists(bucketName, clientName); err == nil { + if err := v.removeOldCertificate(bucketName, clientName); err != nil { + return err + } + } + if err := v.faRunner.ConfigOpenvpnClient(clientName); err != nil { return fmt.Errorf("error configuring openvpn client: %w", err) } @@ -112,6 +132,31 @@ func (v *VpnConnector) GenerateCertificates() error { return nil } +func (v *VpnConnector) getFuryAgentBucketName() (string, error) { + faCfg, err := furyagent.ParseConfig(filepath.Join(v.certDir, "furyagent.yml")) + if err != nil { + return "", fmt.Errorf("error parsing furyagent config: %w", err) + } + + return faCfg.Storage.BucketName, nil +} + +func (v *VpnConnector) assertOldCertificateExists(bucketName, certName string) error { + if _, err := v.awsRunner.S3("ls", fmt.Sprintf("s3://%s/pki/vpn-client/%s.crt", bucketName, certName)); err != nil { + return fmt.Errorf("error checking if old certificate exists: %w", err) + } + + return nil +} + +func (v *VpnConnector) removeOldCertificate(bucketName, certName string) error { + if _, err := v.awsRunner.S3("rm", fmt.Sprintf("s3://%s/pki/vpn-client/%s.crt", bucketName, certName)); err != nil { + return fmt.Errorf("error removing old certificate: %w", err) + } + + return nil +} + func (v *VpnConnector) IsConfigured() bool { vpn := v.config if vpn == nil { diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index a1fa6a38a..a25c75451 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -50,6 +50,22 @@ func (r *Runner) Ec2(sub string, params ...string) (string, error) { return out, nil } +func (r *Runner) S3(params ...string) (string, error) { + args := []string{"s3"} + args = append(args, params...) + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error executing awscli s3: %w", err) + } + + return out, nil +} + func (r *Runner) S3Api(params ...string) (string, error) { args := []string{"s3api"} args = append(args, params...) diff --git a/internal/tool/furyagent/config.go b/internal/tool/furyagent/config.go new file mode 100644 index 000000000..e2f61cff0 --- /dev/null +++ b/internal/tool/furyagent/config.go @@ -0,0 +1,27 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package furyagent + +import ( + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +type Storage struct { + BucketName string `yaml:"bucketName"` +} + +type AgentConfig struct { + Storage Storage `yaml:"storage"` + ClusterComponent map[string]any `yaml:"clusterComponent"` +} + +func ParseConfig(cfgPath string) (*AgentConfig, error) { + cfg, err := yamlx.FromFileV3[*AgentConfig](cfgPath) + if err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index bbb5e800f..d9624f5d1 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -37,7 +37,12 @@ func (r *Runner) CmdPath() string { func (r *Runner) ConfigOpenvpnClient(name string) error { cmd := execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ - Args: []string{"configure", "openvpn-client", fmt.Sprintf("--client-name=%s", name), "--config=furyagent.yml"}, + Args: []string{ + "configure", + "openvpn-client", + fmt.Sprintf("--client-name=%s", name), + "--config=furyagent.yml", + }, Executor: r.executor, WorkDir: r.paths.WorkDir, }) From 336ae4245bc26dbf81a95b031fe9f19b5d35569f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 18:25:17 +0200 Subject: [PATCH 246/383] fix: don't recreate cert if there's already one on local fs --- internal/apis/kfd/v1alpha2/eks/vpn.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 645f2a8d4..761adccc6 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -115,18 +115,22 @@ func (v *VpnConnector) GenerateCertificates() error { return err } - if err := v.assertOldCertificateExists(bucketName, clientName); err == nil { - if err := v.removeOldCertificate(bucketName, clientName); err != nil { - return err + opvnCertPath := filepath.Join(v.certDir, fmt.Sprintf("%s.ovpn", clientName)) + + if _, err := os.Stat(opvnCertPath); os.IsNotExist(err) { + if err := v.assertOldCertificateExists(bucketName, clientName); err == nil { + if err := v.removeOldCertificate(bucketName, clientName); err != nil { + return err + } } - } - if err := v.faRunner.ConfigOpenvpnClient(clientName); err != nil { - return fmt.Errorf("error configuring openvpn client: %w", err) - } + if err := v.faRunner.ConfigOpenvpnClient(clientName); err != nil { + return fmt.Errorf("error configuring openvpn client: %w", err) + } - if err := v.copyOpenvpnToWorkDir(clientName); err != nil { - return fmt.Errorf("error copying openvpn file to workdir: %w", err) + if err := v.copyOpenvpnToWorkDir(clientName); err != nil { + return fmt.Errorf("error copying openvpn file to workdir: %w", err) + } } return nil From 1c4c8da2205b1ca5fc997f1904a152cdaf8f2435 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 14 Apr 2023 19:47:31 +0200 Subject: [PATCH 247/383] feat: removed .crt deletion --- internal/apis/kfd/v1alpha2/eks/creator.go | 165 ++++++++++++---------- internal/apis/kfd/v1alpha2/eks/deleter.go | 5 +- internal/apis/kfd/v1alpha2/eks/vpn.go | 32 +++-- 3 files changed, 115 insertions(+), 87 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index ff5a0fe81..2cafe4e53 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -118,7 +118,7 @@ func (v *ClusterCreator) Create(skipPhase string) error { vpnConfig = v.furyctlConf.Spec.Infrastructure.Vpn } - vpnConnector := NewVpnConnector( + vpnConnector, err := NewVpnConnector( v.furyctlConf.Metadata.Name, infra.SecretsPath, v.paths.BinPath, @@ -127,115 +127,138 @@ func (v *ClusterCreator) Create(skipPhase string) error { v.skipVpn, vpnConfig, ) + if err != nil { + return fmt.Errorf("error while creating vpn connector: %w", err) + } switch v.phase { case cluster.OperationPhaseInfrastructure: - if v.furyctlConf.Spec.Infrastructure == nil { - absPath, err := filepath.Abs(v.paths.ConfigPath) - if err != nil { - logrus.Debugf("error while getting absolute path of %s: %v", v.paths.ConfigPath, err) - - return fmt.Errorf("%w: at %s", ErrInfraNotPresent, v.paths.ConfigPath) - } + if err := v.infraPhase(infra, vpnConnector); err != nil { + return err + } - return fmt.Errorf("%w: check at %s", ErrInfraNotPresent, absPath) + case cluster.OperationPhaseKubernetes: + if err := v.kubernetesPhase(kube, vpnConnector); err != nil { + return err } - if err = infra.Exec(); err != nil { - return fmt.Errorf("error while executing infrastructure phase: %w", err) + case cluster.OperationPhaseDistribution: + if err := v.distributionPhase(distro, vpnConnector); err != nil { + return err } - if v.dryRun { - logrus.Info("Infrastructure created successfully (dry-run mode)") + case cluster.OperationPhaseAll: + return v.allPhases(skipPhase, infra, kube, distro, vpnConnector) - return nil - } + default: + return ErrUnsupportedPhase + } - logrus.Info("Infrastructure created successfully") + return nil +} - if vpnConnector.IsConfigured() { - if err = vpnConnector.GenerateCertificates(); err != nil { - return fmt.Errorf("error while generating vpn certificates: %w", err) - } +func (v *ClusterCreator) infraPhase(infra *create.Infrastructure, vpnConnector *VpnConnector) error { + if v.furyctlConf.Spec.Infrastructure == nil { + absPath, err := filepath.Abs(v.paths.ConfigPath) + if err != nil { + logrus.Debugf("error while getting absolute path of %s: %v", v.paths.ConfigPath, err) + + return fmt.Errorf("%w: at %s", ErrInfraNotPresent, v.paths.ConfigPath) } - return nil + return fmt.Errorf("%w: check at %s", ErrInfraNotPresent, absPath) + } - case cluster.OperationPhaseKubernetes: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && - !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && - !v.dryRun { - if err = vpnConnector.Connect(); err != nil { - return fmt.Errorf("error while connecting to the vpn: %w", err) - } - } + if err := infra.Exec(); err != nil { + return fmt.Errorf("error while executing infrastructure phase: %w", err) + } - logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + - " (e.g. check VPN connection is active`), otherwise the installation will fail.") + if v.dryRun { + logrus.Info("Infrastructure created successfully (dry-run mode)") - if err = kube.Exec(); err != nil { - return fmt.Errorf("error while executing kubernetes phase: %w", err) - } + return nil + } - if v.dryRun { - logrus.Info("Kubernetes cluster created successfully (dry-run mode)") + logrus.Info("Infrastructure created successfully") - return nil + if vpnConnector.IsConfigured() { + if err := vpnConnector.GenerateCertificates(); err != nil { + return fmt.Errorf("error while generating vpn certificates: %w", err) } + } + + return nil +} - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) +func (v *ClusterCreator) kubernetesPhase(kube *create.Kubernetes, vpnConnector *VpnConnector) error { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && + !v.dryRun { + if err := vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) } + } - logrus.Info("Kubernetes cluster created successfully") + logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + + " (e.g. check VPN connection is active`), otherwise the installation will fail.") - if err := v.logKubeconfig(); err != nil { - return fmt.Errorf("error while logging kubeconfig path: %w", err) - } + if err := kube.Exec(); err != nil { + return fmt.Errorf("error while executing kubernetes phase: %w", err) + } - if err := v.logVPNKill(vpnConnector); err != nil { - return fmt.Errorf("error while logging vpn kill message: %w", err) - } + if v.dryRun { + logrus.Info("Kubernetes cluster created successfully (dry-run mode)") return nil + } - case cluster.OperationPhaseDistribution: - if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && - !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && - !v.dryRun { - if err = vpnConnector.Connect(); err != nil { - return fmt.Errorf("error while connecting to the vpn: %w", err) - } - } + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) + } - if err = distro.Exec(); err != nil { - return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) - } + logrus.Info("Kubernetes cluster created successfully") - if v.dryRun { - logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") + if err := v.logKubeconfig(); err != nil { + return fmt.Errorf("error while logging kubeconfig path: %w", err) + } - return nil - } + if err := v.logVPNKill(vpnConnector); err != nil { + return fmt.Errorf("error while logging vpn kill message: %w", err) + } + + return nil +} - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) +func (v *ClusterCreator) distributionPhase(distro *create.Distribution, vpnConnector *VpnConnector) error { + if v.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !v.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && + !v.dryRun { + if err := vpnConnector.Connect(); err != nil { + return fmt.Errorf("error while connecting to the vpn: %w", err) } + } - logrus.Info("Kubernetes Fury Distribution installed successfully") + if err := distro.Exec(); err != nil { + return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) + } - if err := v.logVPNKill(vpnConnector); err != nil { - return fmt.Errorf("error while logging vpn kill message: %w", err) - } + if v.dryRun { + logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") return nil + } - case cluster.OperationPhaseAll: - return v.allPhases(skipPhase, infra, kube, distro, vpnConnector) + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) + } - default: - return ErrUnsupportedPhase + logrus.Info("Kubernetes Fury Distribution installed successfully") + + if err := v.logVPNKill(vpnConnector); err != nil { + return fmt.Errorf("error while logging vpn kill message: %w", err) } + + return nil } func (v *ClusterCreator) allPhases( diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 22fb05f49..36172aa34 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -105,7 +105,7 @@ func (d *ClusterDeleter) Delete() error { vpnConfig = d.furyctlConf.Spec.Infrastructure.Vpn } - vpnConnector := NewVpnConnector( + vpnConnector, err := NewVpnConnector( d.furyctlConf.Metadata.Name, infra.SecretsPath, d.paths.BinPath, @@ -114,6 +114,9 @@ func (d *ClusterDeleter) Delete() error { d.skipVpn, vpnConfig, ) + if err != nil { + return fmt.Errorf("error while creating vpn connector: %w", err) + } switch d.phase { case cluster.OperationPhaseInfrastructure: diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 761adccc6..c9325204d 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -40,6 +40,7 @@ type VpnConnector struct { ovRunner *openvpn.Runner faRunner *furyagent.Runner awsRunner *awscli.Runner + workDir string } func NewVpnConnector( @@ -50,9 +51,14 @@ func NewVpnConnector( autoconnect, skip bool, config *private.SpecInfrastructureVpn, -) *VpnConnector { +) (*VpnConnector, error) { executor := execx.NewStdExecutor() + wd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("error getting current working directory: %w", err) + } + return &VpnConnector{ clusterName: clusterName, certDir: certDir, @@ -60,7 +66,7 @@ func NewVpnConnector( skip: skip, config: config, ovRunner: openvpn.NewRunner(executor, openvpn.Paths{ - WorkDir: certDir, + WorkDir: wd, Openvpn: "openvpn", }), faRunner: furyagent.NewRunner(executor, furyagent.Paths{ @@ -74,7 +80,8 @@ func NewVpnConnector( WorkDir: certDir, }, ), - } + workDir: wd, + }, nil } func (v *VpnConnector) Connect() error { @@ -117,11 +124,14 @@ func (v *VpnConnector) GenerateCertificates() error { opvnCertPath := filepath.Join(v.certDir, fmt.Sprintf("%s.ovpn", clientName)) + wdCertPath := filepath.Join(v.workDir, fmt.Sprintf("%s.ovpn", clientName)) + if _, err := os.Stat(opvnCertPath); os.IsNotExist(err) { if err := v.assertOldCertificateExists(bucketName, clientName); err == nil { - if err := v.removeOldCertificate(bucketName, clientName); err != nil { - return err - } + logrus.Infof("Skipping certificate generation, there is already a certificate for %s, you can use "+ + "the certificate from %s", clientName, wdCertPath) + + return nil } if err := v.faRunner.ConfigOpenvpnClient(clientName); err != nil { @@ -153,14 +163,6 @@ func (v *VpnConnector) assertOldCertificateExists(bucketName, certName string) e return nil } -func (v *VpnConnector) removeOldCertificate(bucketName, certName string) error { - if _, err := v.awsRunner.S3("rm", fmt.Sprintf("s3://%s/pki/vpn-client/%s.crt", bucketName, certName)); err != nil { - return fmt.Errorf("error removing old certificate: %w", err) - } - - return nil -} - func (v *VpnConnector) IsConfigured() bool { vpn := v.config if vpn == nil { @@ -302,7 +304,7 @@ func (v *VpnConnector) prompt() error { return fmt.Errorf("error getting client name: %w", err) } - certPath := filepath.Join(v.certDir, fmt.Sprintf("%s.ovpn", clientName)) + certPath := filepath.Join(v.workDir, fmt.Sprintf("%s.ovpn", clientName)) if v.IsConfigured() { isRoot, err := osx.IsRoot() From 4ceadb230278c9d67ee8d25f7d7af8b50ce3b6d5 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 17 Apr 2023 13:21:42 +0200 Subject: [PATCH 248/383] feat: now we backup and revoke the old client cert --- internal/apis/kfd/v1alpha2/eks/vpn.go | 54 ++++++++++++++++++++++---- internal/tool/furyagent/runner.go | 38 +++++++----------- internal/tool/furyagent/runner_test.go | 10 ----- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index c9325204d..e70d2cf89 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" "github.com/shirou/gopsutil/v3/process" "github.com/sirupsen/logrus" @@ -124,20 +125,27 @@ func (v *VpnConnector) GenerateCertificates() error { opvnCertPath := filepath.Join(v.certDir, fmt.Sprintf("%s.ovpn", clientName)) - wdCertPath := filepath.Join(v.workDir, fmt.Sprintf("%s.ovpn", clientName)) - if _, err := os.Stat(opvnCertPath); os.IsNotExist(err) { - if err := v.assertOldCertificateExists(bucketName, clientName); err == nil { - logrus.Infof("Skipping certificate generation, there is already a certificate for %s, you can use "+ - "the certificate from %s", clientName, wdCertPath) + if err := v.assertOldClientCertificateExists(bucketName, clientName); err == nil { + c, err := v.backupOldClientCertificate(bucketName, clientName) + if err != nil { + return err + } - return nil + if _, err := v.faRunner.ConfigOpenvpnClient(c, "--revoke"); err != nil { + return fmt.Errorf("error configuring openvpn client: %w", err) + } } - if err := v.faRunner.ConfigOpenvpnClient(clientName); err != nil { + out, err := v.faRunner.ConfigOpenvpnClient(clientName) + if err != nil { return fmt.Errorf("error configuring openvpn client: %w", err) } + if err := v.writeOVPNFileToDisk(clientName, out.Bytes()); err != nil { + return err + } + if err := v.copyOpenvpnToWorkDir(clientName); err != nil { return fmt.Errorf("error copying openvpn file to workdir: %w", err) } @@ -155,7 +163,7 @@ func (v *VpnConnector) getFuryAgentBucketName() (string, error) { return faCfg.Storage.BucketName, nil } -func (v *VpnConnector) assertOldCertificateExists(bucketName, certName string) error { +func (v *VpnConnector) assertOldClientCertificateExists(bucketName, certName string) error { if _, err := v.awsRunner.S3("ls", fmt.Sprintf("s3://%s/pki/vpn-client/%s.crt", bucketName, certName)); err != nil { return fmt.Errorf("error checking if old certificate exists: %w", err) } @@ -163,6 +171,36 @@ func (v *VpnConnector) assertOldCertificateExists(bucketName, certName string) e return nil } +func (v *VpnConnector) backupOldClientCertificate(bucketName, certName string) (string, error) { + u := uuid.New() + + newCertName := fmt.Sprintf("%s-%s-backup", certName, u.String()) + + if _, err := v.awsRunner.S3( + "mv", + fmt.Sprintf("s3://%s/pki/vpn-client/%s.crt", bucketName, certName), + fmt.Sprintf("s3://%s/pki/vpn-client/%s.crt", bucketName, newCertName)); err != nil { + return newCertName, fmt.Errorf("error backing up old certificate: %w", err) + } + + return newCertName, nil +} + +func (v *VpnConnector) writeOVPNFileToDisk(certName string, cert []byte) error { + err := os.WriteFile( + filepath.Join(v.faRunner.CmdPath(), + v.certDir, + fmt.Sprintf("%s.ovpn", certName)), + cert, + iox.FullRWPermAccess, + ) + if err != nil { + return fmt.Errorf("error writing openvpn file to disk: %w", err) + } + + return nil +} + func (v *VpnConnector) IsConfigured() bool { vpn := v.config if vpn == nil { diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index d9624f5d1..4a2c45571 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -5,13 +5,10 @@ package furyagent import ( + "bytes" "fmt" - "os" - "path" - "strings" execx "github.com/sighupio/furyctl/internal/x/exec" - iox "github.com/sighupio/furyctl/internal/x/io" ) type Paths struct { @@ -35,34 +32,27 @@ func (r *Runner) CmdPath() string { return r.paths.Furyagent } -func (r *Runner) ConfigOpenvpnClient(name string) error { +func (r *Runner) ConfigOpenvpnClient(name string, params ...string) (*bytes.Buffer, error) { + args := []string{ + "configure", + "openvpn-client", + fmt.Sprintf("--client-name=%s", name), + "--config=furyagent.yml", + } + + args = append(args, params...) + cmd := execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ - Args: []string{ - "configure", - "openvpn-client", - fmt.Sprintf("--client-name=%s", name), - "--config=furyagent.yml", - }, + Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, }) if err := cmd.Run(); err != nil { - if !strings.Contains(err.Error(), "already exists") { - return fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) - } - - return nil - } - - err := os.WriteFile(path.Join(r.paths.WorkDir, - fmt.Sprintf("%s.ovpn", name)), - cmd.Log.Out.Bytes(), iox.FullRWPermAccess) - if err != nil { - return fmt.Errorf("error writing openvpn client config: %w", err) + return nil, fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) } - return nil + return cmd.Log.Out, nil } func (r *Runner) Version() (string, error) { diff --git a/internal/tool/furyagent/runner_test.go b/internal/tool/furyagent/runner_test.go index c3ac60c01..f33ad7c60 100644 --- a/internal/tool/furyagent/runner_test.go +++ b/internal/tool/furyagent/runner_test.go @@ -9,7 +9,6 @@ package furyagent_test import ( "fmt" "os" - "path/filepath" "testing" "github.com/sighupio/furyctl/internal/tool/furyagent" @@ -44,15 +43,6 @@ func Test_Runner_ConfigOpenvpnClient(t *testing.T) { if err != nil { t.Fatal(err) } - - info, err := os.Stat(filepath.Join(os.TempDir(), "furyctltest.ovpn")) - if err != nil { - t.Fatal(err) - } - - if info.Size() == 0 { - t.Error("expected file to be not empty") - } } func TestHelperProcess(t *testing.T) { From e955a8e15c3c13a6cabb41150f7c067e59247b56 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 17 Apr 2023 15:23:27 +0200 Subject: [PATCH 249/383] fix: unit tests --- go.mod | 2 +- internal/tool/furyagent/runner_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ca96841c9..324a2a4fc 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/dukex/mixpanel v1.0.1 github.com/go-playground/validator/v10 v10.11.1 github.com/google/go-cmp v0.5.9 + github.com/google/uuid v1.3.0 github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/terraform-json v0.14.0 github.com/miekg/dns v1.1.50 @@ -46,7 +47,6 @@ require ( github.com/go-playground/universal-translator v0.18.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/internal/tool/furyagent/runner_test.go b/internal/tool/furyagent/runner_test.go index f33ad7c60..81e4c3e7e 100644 --- a/internal/tool/furyagent/runner_test.go +++ b/internal/tool/furyagent/runner_test.go @@ -39,7 +39,7 @@ func Test_Runner_ConfigOpenvpnClient(t *testing.T) { WorkDir: os.TempDir(), }) - err := r.ConfigOpenvpnClient("furyctltest") + _, err := r.ConfigOpenvpnClient("furyctltest") if err != nil { t.Fatal(err) } From 04c985fc4128c9d31caf8d779e10c4940c9a3d18 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Mon, 17 Apr 2023 16:29:10 +0200 Subject: [PATCH 250/383] chore: added log messages --- internal/apis/kfd/v1alpha2/eks/vpn.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index e70d2cf89..aeda4f46d 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -127,16 +127,22 @@ func (v *VpnConnector) GenerateCertificates() error { if _, err := os.Stat(opvnCertPath); os.IsNotExist(err) { if err := v.assertOldClientCertificateExists(bucketName, clientName); err == nil { + logrus.Info("Old VPN client certificate found. Backing up...") + c, err := v.backupOldClientCertificate(bucketName, clientName) if err != nil { return err } + logrus.Info("Revoking old VPN client certificate...") + if _, err := v.faRunner.ConfigOpenvpnClient(c, "--revoke"); err != nil { return fmt.Errorf("error configuring openvpn client: %w", err) } } + logrus.Info("Generating new VPN client certificate...") + out, err := v.faRunner.ConfigOpenvpnClient(clientName) if err != nil { return fmt.Errorf("error configuring openvpn client: %w", err) From 42249ac066c2e997166af477302cc45282d4aee5 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 17 Apr 2023 20:13:02 +0200 Subject: [PATCH 251/383] fix: add --all flag --- internal/tool/kubectl/runner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 4b097166f..e4b89e500 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -160,7 +160,7 @@ func (r *Runner) DeleteResources(ns, res string) (string, error) { // DeleteResourcesInAllNamespaces deletes the specified resources in all namespaces. func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { - args := []string{"delete", "--all-namespaces", res} + args := []string{"delete", "--all-namespaces", "--all", res} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) From 4bc475247732b270800c0c977e75ec4a4c6db454 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 18 Apr 2023 10:03:57 +0200 Subject: [PATCH 252/383] fix: missing region from vpc connectivity check --- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 2 ++ internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 62df46f76..875ef64ed 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -1150,6 +1150,8 @@ func (k *Kubernetes) checkVPCConnection() error { string(*vpcID), "--query", "Vpcs[0].CidrBlock", + "--region", + string(k.furyctlConf.Spec.Region), "--output", "text", ) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index a984a0971..1c8377fc1 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -159,6 +159,8 @@ func (k *Kubernetes) checkVPCConnection() error { string(*vpcID), "--query", "Vpcs[0].CidrBlock", + "--region", + string(k.furyctlConf.Spec.Region), "--output", "text", ) From c8a8243a3d84677c012caefd34353095784b2d57 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 18 Apr 2023 11:46:28 +0200 Subject: [PATCH 253/383] hotfix: cert dir is wrong --- internal/apis/kfd/v1alpha2/eks/vpn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index aeda4f46d..e1c282e6d 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -194,7 +194,7 @@ func (v *VpnConnector) backupOldClientCertificate(bucketName, certName string) ( func (v *VpnConnector) writeOVPNFileToDisk(certName string, cert []byte) error { err := os.WriteFile( - filepath.Join(v.faRunner.CmdPath(), + filepath.Join( v.certDir, fmt.Sprintf("%s.ovpn", certName)), cert, From 226b8a93a74ca9da241a878a3bc01fce2bb22cd9 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 18 Apr 2023 11:53:39 +0200 Subject: [PATCH 254/383] fix: add check for resource types existance before running deletes. --- .../kfd/v1alpha2/eks/delete/distribution.go | 88 +++++++++---------- internal/kubernetes/client.go | 13 +++ internal/tool/kubectl/runner.go | 21 +++++ 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 44d07c732..51ad5eb8f 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -316,80 +316,80 @@ func (d *Distribution) deleteIngresses() error { } func (d *Distribution) deleteBlockingResources() error { - dur := time.Minute * ingressAfterDeleteDelay - - logrus.Info("Deleting prometheus resources...") - - _, err := d.kubeClient.DeleteResources("prometheus", "monitoring") - if err != nil { - return fmt.Errorf("error deleting prometheus resources: %w", err) + if err := d.deleteResource("deployment", "logging", "loki-distributed-distributor"); err != nil { + return err } - logrus.Info("Deleting PersistentVolumeClaims in the namespace 'monitoring'...") + if err := d.deleteResource("deployment", "logging", "loki-distributed-compactor"); err != nil { + return err + } - _, err = d.kubeClient.DeleteResources("pvc", "monitoring") - if err != nil { - return fmt.Errorf("error deleting pvc in namespace 'monitoring': %w", err) + if err := d.deleteResources("prometheus", "monitoring"); err != nil { + return err } - logrus.Info("Deleting logging resources...") + if err := d.deleteResources("pvc", "monitoring"); err != nil { + return err + } - _, err = d.kubeClient.DeleteResources("logging", "logging") - if err != nil { - return fmt.Errorf("error deleting logging resources: %w", err) + if err := d.deleteResources("logging", "logging"); err != nil { + return err } - logrus.Info("Deleting Deployments in the namespace 'logging'...") + if err := d.deleteResources("sts", "logging"); err != nil { + return err + } - resExists, err := d.kubeClient.ResourceExists("loki-distributed-distributor", "deployment", "logging") - if err != nil { - return fmt.Errorf("error checking if resource 'loki-distributed-distributor' exists in logging namespace: %w", err) + if err := d.deleteResources("pvc", "logging"); err != nil { + return err } - if resExists { - _, err = d.kubeClient.DeleteResource("loki-distributed-distributor", "deployment", "logging") - if err != nil { - return fmt.Errorf("error deleting deployment 'loki-distributed-distributor' in logging namespace: %w", err) - } + if err := d.deleteResources("svc", "ingress-nginx"); err != nil { + return err } - resExists, err = d.kubeClient.ResourceExists("loki-distributed-compactor", "deployment", "logging") + logrus.Debugf("waiting for resources to be deleted...") + + time.Sleep(time.Minute * ingressAfterDeleteDelay) + + return nil +} + +func (d *Distribution) deleteResource(typ, ns, name string) error { + logrus.Infof("Deleting %ss '%s' in namespace '%s'...\n", typ, name, ns) + + resExists, err := d.kubeClient.ResourceExists(name, typ, ns) if err != nil { - return fmt.Errorf("error checking if resource 'loki-distributed-distributor' exists in logging namespace: %w", err) + return fmt.Errorf("error checking if %s '%s' exists in '%s' namespace: %w", typ, name, ns, err) } if resExists { - _, err = d.kubeClient.DeleteResource("loki-distributed-compactor", "deployment", "logging") + _, err = d.kubeClient.DeleteResource(name, typ, ns) if err != nil { - return fmt.Errorf("error deleting deployment 'loki-distributed-compactor' in logging namespace: %w", err) + return fmt.Errorf("error deleting %s '%s' in '%s' namespace: %w", typ, name, ns, err) } } - logrus.Info("Deleting StafultSets in the namespace 'logging'...") - - _, err = d.kubeClient.DeleteResources("sts", "logging") - if err != nil { - return fmt.Errorf("error deleting sts in namespace 'logging': %w", err) - } + return nil +} - logrus.Info("Deleting PersistentVolumeClaims in the namespace 'logging'...") +func (d *Distribution) deleteResources(typ, ns string) error { + logrus.Infof("Deleting %ss in namespace '%s'...\n", typ, ns) - _, err = d.kubeClient.DeleteResources("pvc", "logging") + hasResTyp, err := d.kubeClient.HasResourceType(typ) if err != nil { - return fmt.Errorf("error deleting pvc in namespace 'logging': %w", err) + return fmt.Errorf("error checking '%s' resources type: %w", typ, err) } - logrus.Info("Deleting Services in the namespace 'ingress-nginx'...") + if !hasResTyp { + return nil + } - _, err = d.kubeClient.DeleteResources("svc", "ingress-nginx") + _, err = d.kubeClient.DeleteResources(typ, ns) if err != nil { - return fmt.Errorf("error deleting svc in namespace 'ingress-nginx': %w", err) + return fmt.Errorf("error deleting '%s' in namespace '%s': %w", typ, ns, err) } - logrus.Debugf("waiting for resources to be deleted...") - - time.Sleep(dur) - return nil } diff --git a/internal/kubernetes/client.go b/internal/kubernetes/client.go index 1a9721947..6933b3da8 100644 --- a/internal/kubernetes/client.go +++ b/internal/kubernetes/client.go @@ -10,6 +10,8 @@ import ( "regexp" "strings" + goslices "golang.org/x/exp/slices" + "github.com/sighupio/furyctl/internal/tool/kubectl" execx "github.com/sighupio/furyctl/internal/x/exec" "github.com/sighupio/furyctl/internal/x/slices" @@ -56,6 +58,17 @@ func NewClient( } } +func (c *Client) HasResourceType(typ string) (bool, error) { + cmdOut, err := c.kubeRunner.APIResources("--sort-by", "name", "-o", "name") + if err != nil { + return false, fmt.Errorf("error while listing cluster resource types: %w", err) + } + + results := strings.Split(cmdOut, "\n") + + return goslices.Index(results, typ) != -1, nil +} + // GetIngresses returns a list of ingresses in the cluster, this is done by using the jsonpath format option // of kubectl to get a valid json output to be unmarshalled. func (c *Client) GetIngresses() ([]Ingress, error) { diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index e4b89e500..164fd6a11 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -99,6 +99,27 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { return out, nil } +func (r *Runner) APIResources(params ...string) (string, error) { + args := []string{"api-resources"} + + if r.paths.Kubeconfig != "" { + args = append(args, "--kubeconfig", r.paths.Kubeconfig) + } + + args = append(args, params...) + + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return out, fmt.Errorf("error while listing api resources: %w", err) + } + + return out, nil +} + func (r *Runner) GetResource(ns, res, name string) (string, error) { args := []string{"get", res, "-n", ns, name} From a36bf2d2b18eadd37781af460c79b0f7468d731e Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 18 Apr 2023 12:32:20 +0200 Subject: [PATCH 255/383] chore: temporarily disable latest version check --- cmd/root.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 602d01aa5..880150330 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -49,10 +49,9 @@ func NewRootCommand( tracker *analytics.Tracker, token string, ) *RootCommand { - // Update channels. - r := make(chan app.Release, 1) - e := make(chan error, 1) - // Analytics event channel. + // // Update channels. + // r := make(chan app.Release, 1) + // e := make(chan error, 1) cfg := &rootConfig{} rootCmd := &RootCommand{ @@ -80,8 +79,8 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber } } - // Async check for updates. - go checkUpdates(versions["version"], r, e) + // // Async check for updates. + // go checkUpdates(versions["version"], r, e) // Configure the spinner. w := logrus.StandardLogger().Out @@ -144,19 +143,19 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber logrus.Debug("FURYCTL_MIXPANEL_TOKEN is not set") } }, - PersistentPostRun: func(_ *cobra.Command, _ []string) { - // Show update message if available at the end of the command. - select { - case release := <-r: - if shouldUpgrade(release.Version, versions["version"]) { - logrus.Infof("A newer version of furyctl is available: %s => %s", versions["version"], release.Version) - } - case err := <-e: - if err != nil { - logrus.Debugf("Error checking for updates to furyctl: %s", err) - } - } - }, + // PersistentPostRun: func(_ *cobra.Command, _ []string) { + // // Show update message if available at the end of the command. + // select { + // case release := <-r: + // if shouldUpgrade(release.Version, versions["version"]) { + // logrus.Infof("A newer version of furyctl is available: %s => %s", versions["version"], release.Version) + // } + // case err := <-e: + // if err != nil { + // logrus.Debugf("Error checking for updates to furyctl: %s", err) + // } + // } + // }, }, config: cfg, } From 3db1fc8e7a0b985f32c8ebc10294416ac2005cef Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 18 Apr 2023 12:33:00 +0200 Subject: [PATCH 256/383] fix: add guard against slice key accesses --- cmd/version.go | 2 +- internal/semver/compare.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index eac1ca1f3..009dfe193 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -21,7 +21,7 @@ func NewVersionCmd(versions map[string]string, tracker *analytics.Tracker) *cobr return &cobra.Command{ Use: "version", Short: "Print the version number of furyctl", - PreRun: func(cmd *cobra.Command, args []string) { + PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, Run: func(_ *cobra.Command, _ []string) { diff --git a/internal/semver/compare.go b/internal/semver/compare.go index dff1599f0..5a3e2bf64 100644 --- a/internal/semver/compare.go +++ b/internal/semver/compare.go @@ -18,6 +18,10 @@ var ( ) func NewVersion(v string) (*version.Version, error) { + if len(v) == 0 { + return nil, fmt.Errorf("%w: version is empty", ErrInvalidVersion) + } + vStr := v if v[0] == 'v' { @@ -26,13 +30,17 @@ func NewVersion(v string) (*version.Version, error) { ver, err := version.NewVersion(vStr) if err != nil { - return nil, ErrInvalidVersion + return nil, fmt.Errorf("%w: %v", ErrInvalidVersion, err) } return ver, nil } func NewConstraint(c string) (version.Constraints, error) { + if len(c) == 0 { + return nil, fmt.Errorf("%w: constraint is empty", ErrInvalidConstraint) + } + cStr := c if c[0] == 'v' { @@ -41,7 +49,7 @@ func NewConstraint(c string) (version.Constraints, error) { cnst, err := version.NewConstraint(cStr) if err != nil { - return nil, ErrInvalidConstraint + return nil, fmt.Errorf("%w: %v", ErrInvalidConstraint, err) } return cnst, nil From 97b475958717126b4bb6fe9f41029bdd8ac725f2 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 18 Apr 2023 16:34:22 +0200 Subject: [PATCH 257/383] fix: do not ask for vpn connection when panel is mixed private/public --- internal/apis/kfd/v1alpha2/eks/deleter.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index 36172aa34..bb1272772 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -167,7 +167,9 @@ func (d *ClusterDeleter) Delete() error { "Sometimes this is not possible, for better results limit the scope with the --phase flag.") } - if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !d.dryRun { + if d.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !d.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess && + !d.dryRun { if err := vpnConnector.Connect(); err != nil { return fmt.Errorf("error while connecting to the vpn: %w", err) } From 69b238b8db9818866eaab6c611b557a938ab327a Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 18 Apr 2023 17:02:40 +0200 Subject: [PATCH 258/383] chore: removed vpn message when deleting on public access cluster --- internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index 37068ba11..2a37958c0 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -87,7 +87,9 @@ func (i *Infrastructure) Exec() error { return fmt.Errorf("error while deleting infrastructure: %w", err) } - if i.isVpnConfigured() { + if i.isVpnConfigured() && + i.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && + !i.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess { killMsg := "killall openvpn" isRoot, err := osx.IsRoot() From 61fa8a33d4ba95baa86ff108f7e43ce5d89839c8 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 19 Apr 2023 10:02:32 +0200 Subject: [PATCH 259/383] fix: delete blocking resources --- .../apis/kfd/v1alpha2/eks/delete/distribution.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 51ad5eb8f..7d38df4d0 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -324,27 +324,31 @@ func (d *Distribution) deleteBlockingResources() error { return err } - if err := d.deleteResources("prometheus", "monitoring"); err != nil { + if err := d.deleteResources("prometheuses.monitoring.coreos.com", "monitoring"); err != nil { return err } - if err := d.deleteResources("pvc", "monitoring"); err != nil { + if err := d.deleteResources("prometheusrules.monitoring.coreos.com", "monitoring"); err != nil { return err } - if err := d.deleteResources("logging", "logging"); err != nil { + if err := d.deleteResources("persistentvolumeclaims", "monitoring"); err != nil { return err } - if err := d.deleteResources("sts", "logging"); err != nil { + if err := d.deleteResources("loggings.logging.banzaicloud.io", "logging"); err != nil { return err } - if err := d.deleteResources("pvc", "logging"); err != nil { + if err := d.deleteResources("statefulsets", "logging"); err != nil { return err } - if err := d.deleteResources("svc", "ingress-nginx"); err != nil { + if err := d.deleteResources("persistentvolumeclaims", "logging"); err != nil { + return err + } + + if err := d.deleteResources("services", "ingress-nginx"); err != nil { return err } From 99cf058f4d426f7b29c2a0042f05794e1b2d4b23 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 19 Apr 2023 10:44:59 +0200 Subject: [PATCH 260/383] chore: remove unused code for checking available updates --- cmd/root.go | 63 ----------------------------------------------------- 1 file changed, 63 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 880150330..529281cad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,8 +16,6 @@ import ( "github.com/spf13/viper" "github.com/sighupio/furyctl/internal/analytics" - "github.com/sighupio/furyctl/internal/app" - "github.com/sighupio/furyctl/internal/semver" cobrax "github.com/sighupio/furyctl/internal/x/cobra" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" @@ -49,10 +47,6 @@ func NewRootCommand( tracker *analytics.Tracker, token string, ) *RootCommand { - // // Update channels. - // r := make(chan app.Release, 1) - // e := make(chan error, 1) - cfg := &rootConfig{} rootCmd := &RootCommand{ Command: &cobra.Command{ @@ -79,8 +73,6 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber } } - // // Async check for updates. - // go checkUpdates(versions["version"], r, e) // Configure the spinner. w := logrus.StandardLogger().Out @@ -143,19 +135,6 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber logrus.Debug("FURYCTL_MIXPANEL_TOKEN is not set") } }, - // PersistentPostRun: func(_ *cobra.Command, _ []string) { - // // Show update message if available at the end of the command. - // select { - // case release := <-r: - // if shouldUpgrade(release.Version, versions["version"]) { - // logrus.Infof("A newer version of furyctl is available: %s => %s", versions["version"], release.Version) - // } - // case err := <-e: - // if err != nil { - // logrus.Debugf("Error checking for updates to furyctl: %s", err) - // } - // } - // }, }, config: cfg, } @@ -211,48 +190,6 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber return rootCmd } -func shouldUpgrade(releaseVersion, currentVersion string) bool { - if releaseVersion == "unknown" { - return false - } - - relV, err := semver.NewVersion(releaseVersion) - if err != nil { - logrus.Debugf("Error parsing release version: %s", err) - - return false - } - - curV, err := semver.NewVersion(currentVersion) - if err != nil { - logrus.Debugf("Error parsing current version: %s", err) - - return false - } - - return relV.GreaterThan(curV) -} - -func checkUpdates(version string, rc chan app.Release, e chan error) { - defer close(rc) - defer close(e) - - if version == "unknown" { - rc <- app.Release{Version: version} - - return - } - - r, err := app.GetLatestRelease() - if err != nil { - e <- err - - return - } - - rc <- r -} - func createLogFile(path string) (*os.File, error) { // Create the log directory if it doesn't exist. if err := os.MkdirAll(filepath.Dir(path), iox.UserGroupPerm); err != nil { From 80edbf4793bcf7b9de64c84810c40e3075179a69 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 19 Apr 2023 11:17:59 +0200 Subject: [PATCH 261/383] fix: cannot use kubeconfig when not running distribution phase alone --- cmd/create/cluster.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index d8cc687e2..46cb10bf7 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -75,6 +75,15 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { kubeconfigPath := flags.Kubeconfig + if kubeconfigPath != "" && + (flags.Phase != cluster.OperationPhaseDistribution || + flags.SkipPhase != cluster.OperationPhaseKubernetes) { + return fmt.Errorf( + "%w: --kubeconfig flag can only be used when running distribution phase alone", + ErrParsingFlag, + ) + } + // Check if kubeconfig is needed. if flags.Phase == cluster.OperationPhaseDistribution || flags.SkipPhase == cluster.OperationPhaseKubernetes { if kubeconfigPath == "" { From 37612015458eb326fad721222a9c072a7ebfcfa9 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 19 Apr 2023 14:46:22 +0200 Subject: [PATCH 262/383] fix: deleting also statefulsets --- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 7d38df4d0..81cd2a96e 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -340,7 +340,7 @@ func (d *Distribution) deleteBlockingResources() error { return err } - if err := d.deleteResources("statefulsets", "logging"); err != nil { + if err := d.deleteResources("statefulsets.apps", "logging"); err != nil { return err } From 1f06cb58213f4ce5592edbc21075027e3699b21b Mon Sep 17 00:00:00 2001 From: omissis Date: Wed, 19 Apr 2023 16:58:15 +0200 Subject: [PATCH 263/383] fix: improve logging messages. --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 4 ++-- internal/apis/kfd/v1alpha2/eks/create/infrastructure.go | 4 ++-- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 4 ++-- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 4 ++-- internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 78962c535..059f5c701 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -14,9 +14,9 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" @@ -202,7 +202,7 @@ func (d *Distribution) Exec() error { return nil } - logrus.Info("Creating cloud resources, this could take a while...") + logrus.Warn("Creating cloud resources, this could take a while...") _, err = d.tfRunner.Apply(timestamp) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index f1216c84d..5ee49a39b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -15,9 +15,9 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" @@ -111,7 +111,7 @@ func (i *Infrastructure) Exec() error { return nil } - logrus.Info("Creating cloud resources, this could take a while...") + logrus.Warn("Creating cloud resources, this could take a while...") if _, err := i.tfRunner.Apply(timestamp); err != nil { return fmt.Errorf("cannot create cloud resources: %w", err) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 875ef64ed..a17a5434a 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -18,9 +18,9 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/merge" @@ -168,7 +168,7 @@ func (k *Kubernetes) Exec() error { } } - logrus.Info("Creating cloud resources, this could take a while...") + logrus.Warn("Creating cloud resources, this could take a while...") out, err := k.tfRunner.Apply(timestamp) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 81cd2a96e..25d40a85b 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -15,8 +15,8 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/kubernetes" "github.com/sighupio/furyctl/internal/tool/awscli" @@ -217,7 +217,7 @@ func (d *Distribution) Exec() error { return err } - logrus.Info("Deleting manifests...") + logrus.Info("Deleting kubernetes resources...") _, err = d.kubeClient.DeleteFromPath(manifestsOutPath) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 1c8377fc1..755508f03 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -12,9 +12,9 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" @@ -129,7 +129,7 @@ func (k *Kubernetes) Exec() error { return nil } - logrus.Info("Deleting cloud resources, this could take a while...") + logrus.Warn("Deleting cloud resources, this could take a while...") err = k.tfRunner.Destroy() if err != nil { From 26daa63767beea052c577ca19af33f38db5fd0be Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 19 Apr 2023 18:20:30 +0200 Subject: [PATCH 264/383] chore: fix linting issues --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 2 +- internal/apis/kfd/v1alpha2/eks/create/infrastructure.go | 2 +- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 2 +- internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 2 +- internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 059f5c701..9740cbf2c 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -14,9 +14,9 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" - "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 5ee49a39b..13ded6ed3 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -15,9 +15,9 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" - "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/template" diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index a17a5434a..531c94455 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -18,9 +18,9 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" - "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/merge" diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 25d40a85b..cc34af819 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -15,8 +15,8 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/kubernetes" "github.com/sighupio/furyctl/internal/tool/awscli" diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 755508f03..6e01ad1f6 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -12,9 +12,9 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/config" "github.com/sighupio/fury-distribution/pkg/schema/private" - "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" From fb37e1d08156bd6af33e1f13b31454b61b0f37de Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 19 Apr 2023 18:44:42 +0200 Subject: [PATCH 265/383] fix: add missing cleanup and dep in ci's release step --- .drone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.drone.yml b/.drone.yml index f01b3c049..cf0e39544 100644 --- a/.drone.yml +++ b/.drone.yml @@ -99,6 +99,9 @@ steps: - (test ! $(aws s3api get-bucket-location --bucket furyctl-e2e-deps-test --output text --no-cli-pager 2>/dev/null | grep "$${AWS_DEFAULT_REGION}")) && aws s3 mb s3://furyctl-e2e-deps-test --region $${AWS_DEFAULT_REGION} # Run tests - make test-e2e + # Cleanup + - rm -rf .go/ aws/ + - rm awscliv2.zip glibc-$${GLIBC_VER}.apk glibc-bin-$${GLIBC_VER}.apk glibc-i18n-$${GLIBC_VER}.apk environment: CGO_ENABLED: 0 GOCACHE: /drone/src/.go/cache @@ -149,6 +152,7 @@ steps: - lint - test-unit - test-integration + - test-e2e commands: - git reset --hard - git fetch --tags From 725160ee177c08d31d9d98fc65ef1e490b8eac31 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 19 Apr 2023 18:48:01 +0200 Subject: [PATCH 266/383] fix: change deps for build step in ci --- .drone.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index cf0e39544..bb4a77ffb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -125,7 +125,10 @@ steps: - name: build image: ghcr.io/goreleaser/goreleaser:v1.15.2 depends_on: - - prepare + - lint + - test-unit + - test-integration + - test-e2e pull: always commands: - git reset --hard From b2bb8e74fe38c3e5c8316269cee499d7e83b97ae Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 19 Apr 2023 18:54:21 +0200 Subject: [PATCH 267/383] fix: remove removal of .go file in ci --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index bb4a77ffb..a102885f8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -100,7 +100,7 @@ steps: # Run tests - make test-e2e # Cleanup - - rm -rf .go/ aws/ + - rm -rf aws/ - rm awscliv2.zip glibc-$${GLIBC_VER}.apk glibc-bin-$${GLIBC_VER}.apk glibc-i18n-$${GLIBC_VER}.apk environment: CGO_ENABLED: 0 From 4e17650b90484462a08d98e86dbfe043cf4c995e Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 19 Apr 2023 19:17:52 +0200 Subject: [PATCH 268/383] fix: git ignore .go cache directory --- .drone.yml | 1 - .gitignore | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index a102885f8..ef6119a4b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -150,7 +150,6 @@ steps: - name: build-release image: ghcr.io/goreleaser/goreleaser:v1.15.2 - pull: always depends_on: - lint - test-unit diff --git a/.gitignore b/.gitignore index e20dc4bd3..08b1b16ab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ coverprofile.out vendor bin .idea +.go/ furyctl demo dist/ From bbb738b82bba32175ed8c8cd64ac8444c17ed6df Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Thu, 20 Apr 2023 16:00:39 +0200 Subject: [PATCH 269/383] chore: UX improvements - Change VPN skip flag in create command to be consistent with the other flags. - Improve flags and commands descriptions - Improve some log messages - Align description of flags between commands --- cmd/create/cluster.go | 26 +++++++++++++------------- cmd/delete/cluster.go | 24 ++++++++++++------------ cmd/download.go | 2 +- cmd/download/dependencies.go | 12 ++++++------ cmd/validate.go | 2 +- cmd/validate/config.go | 6 +++--- cmd/validate/dependencies.go | 10 +++++----- cmd/version.go | 2 +- internal/apis/kfd/v1alpha2/eks/vpn.go | 10 +++++----- 9 files changed, 47 insertions(+), 47 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 46cb10bf7..b44022dcc 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -275,9 +275,9 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm binPath := cmdutil.StringFlagOptional(cmd, "bin-path") - skipVpn, err := cmdutil.BoolFlag(cmd, "vpn-skip", tracker, cmdEvent) + skipVpn, err := cmdutil.BoolFlag(cmd, "skip-vpn-confirmation", tracker, cmdEvent) if err != nil { - return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-skip") + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-vpn-confirmation") } vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) @@ -347,13 +347,13 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "phase", "p", "", - "Limit the execution to a specific phase. options are: infrastructure, kubernetes, distribution", + "Limit the execution to a specific phase. Options are: infrastructure, kubernetes, distribution", ) cmd.Flags().String( "skip-phase", "", - "Avoid executing a unwanted phase. options are: infrastructure, kubernetes, distribution. More specifically:\n"+ + "Avoid executing unwanted phases. Options are: infrastructure, kubernetes, distribution. More specifically:\n"+ "- skipping infrastructure will execute kubernetes and distribution\n"+ "- skipping kubernetes will only execute distribution\n"+ "- skipping distribution will execute infrastructure and kubernetes\n", @@ -363,9 +363,9 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "distro-location", "", "", - "Location where to download schemas, defaults and the distribution manifest. "+ - "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?ref=BRANCH_NAME&depth=1). "+ + "Location where to download schemas, defaults and the distribution manifests from. "+ + "It can either be a local path (eg: /path/to/fury/distribution) or "+ + "a remote URL (eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME). "+ "Any format supported by hashicorp/go-getter can be used.", ) @@ -373,7 +373,7 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "bin-path", "b", "", - "Path to the bin folder where all dependencies are installed", + "Path to the folder where all the dependencies' binaries are installed", ) cmd.Flags().Bool( @@ -397,19 +397,19 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { cmd.Flags().Bool( "vpn-auto-connect", false, - "When set will automatically connect to the created VPN in the infrastructure phase, "+ - "please note that this will require OpenVPN to be installed in your system", + "When set will automatically connect to the created VPN by the infrastructure phase "+ + "(requires OpenVPN installed in the system)", ) cmd.Flags().Bool( - "vpn-skip", + "skip-vpn-confirmation", false, - "When set will not wait for user confirmation to connect to the VPN", + "When set will not wait for user confirmation that the VPN is connected", ) cmd.Flags().String( "kubeconfig", "", - "Path to the kubeconfig file, mandatory if you want to run the distribution phase and the KUBECONFIG environment variable is not set", + "Path to the kubeconfig file, mandatory if you want to run the distribution phase alone and the KUBECONFIG environment variable is not set", ) } diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index ac13154e3..c7e9db952 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -203,9 +203,9 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "distro-location", "", "", - "Base URL used to download schemas, defaults and the distribution manifest. "+ - "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ + "Location where to download schemas, defaults and the distribution manifests from. "+ + "It can either be a local path (eg: /path/to/fury/distribution) or "+ + "a remote URL (eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME). "+ "Any format supported by hashicorp/go-getter can be used.", ) @@ -213,14 +213,14 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { "bin-path", "b", "", - "Path to the bin folder where all dependencies are installed", + "Path to the folder where all the dependencies' binaries are installed", ) cmd.Flags().StringP( "phase", "p", "", - "Limit execution to the specified phase", + "Limit execution to the specified phase. Options are: infrastructure, kubernetes, distribution", ) cmd.Flags().Bool( @@ -232,20 +232,20 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { cmd.Flags().Bool( "vpn-auto-connect", false, - "When set will automatically connect to the created VPN in the infrastructure phase, "+ - "please note that this will require OpenVPN to be installed in your system", + "When set will automatically connect to the created VPN by the infrastructure phase "+ + "(requires OpenVPN installed in the system)", ) cmd.Flags().Bool( - "vpn-skip", + "skip-vpn-confirmation", false, - "When set will not wait for user confirmation to connect to the VPN", + "When set will not wait for user confirmation that the VPN is connected", ) cmd.Flags().Bool( "force", false, - "WARNING: furyctl won't ask for confirmation and will force delete the cluster and it resources.", + "WARNING: furyctl won't ask for confirmation and will force delete the cluster and its resources.", ) cmd.Flags().String( @@ -286,9 +286,9 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm binPath := cmdutil.StringFlagOptional(cmd, "bin-path") - skipVpn, err := cmdutil.BoolFlag(cmd, "vpn-skip", tracker, cmdEvent) + skipVpn, err := cmdutil.BoolFlag(cmd, "skip-vpn-confirmation", tracker, cmdEvent) if err != nil { - return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "vpn-skip") + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "skip-vpn-confirmation") } vpnAutoConnect, err := cmdutil.BoolFlag(cmd, "vpn-auto-connect", tracker, cmdEvent) diff --git a/cmd/download.go b/cmd/download.go index adba6f223..d328d0170 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -14,7 +14,7 @@ import ( func NewDownloadCmd(tracker *analytics.Tracker) *cobra.Command { dumpCmd := &cobra.Command{ Use: "download", - Short: "Download all dependencies for the Kubernetes Fury Distribution specified in the configuration file", + Short: "Download all dependencies for the Kubernetes Fury Distribution version specified in the configuration file", } dumpCmd.AddCommand(download.NewDependenciesCmd(tracker)) diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 5857be4ac..39a79cc16 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -31,7 +31,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", - Short: "Download all dependencies from the Fury Distribution specified in the config file", + Short: "Download all dependencies for the Fury Distribution version specified in the configuration file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, @@ -85,7 +85,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { errs, uts := depsdl.DownloadAll(dres.DistroManifest) for _, ut := range uts { - logrus.Warn(fmt.Sprintf("'%s' download is not supported, please install it manually", ut)) + logrus.Warn(fmt.Sprintf("'%s' download is not supported, please install it manually if not present", ut)) } if len(errs) > 0 { @@ -114,7 +114,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "bin-path", "b", "", - "Path to the bin folder where all dependencies are installed", + "Path to the folder where all the dependencies' binaries are installed", ) cmd.Flags().StringP( @@ -128,9 +128,9 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "distro-location", "", "", - "Base URL used to download schemas, defaults and the distribution manifest. "+ - "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ + "Location where to download schemas, defaults and the distribution manifests from. "+ + "It can either be a local path (eg: /path/to/fury/distribution) or "+ + "a remote URL (eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME). "+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/validate.go b/cmd/validate.go index 2cc69eed1..7dee89675 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -14,7 +14,7 @@ import ( func NewValidateCommand(tracker *analytics.Tracker) *cobra.Command { validateCmd := &cobra.Command{ Use: "validate", - Short: "Validate a configuration file and the dependencies relative to the Kubernetes Fury Distribution specified in it", + Short: "Validate a configuration file and the dependencies relative to the Kubernetes Fury Distribution version specified in it", } validateCmd.AddCommand(validate.NewConfigCmd(tracker)) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 2088fc435..982e16c26 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -91,9 +91,9 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "distro-location", "", "", - "Base URL used to download schemas, defaults and the distribution manifest. "+ - "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ + "Location where to download schemas, defaults and the distribution manifests from. "+ + "It can either be a local path (eg: /path/to/fury/distribution) or "+ + "a remote URL (eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME). "+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 3758ccdd9..5ccad64b2 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -30,7 +30,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { cmd := &cobra.Command{ Use: "dependencies", - Short: "Validate dependencies for the Kubernetes Fury Distribution specified in the configuration file", + Short: "Validate dependencies for the Kubernetes Fury Distribution version specified in the configuration file", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, @@ -141,7 +141,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "bin-path", "b", "", - "Path to the bin folder where all dependencies are installed", + "Path to the folder where all the dependencies' binaries are installed", ) cmd.Flags().StringP( @@ -155,9 +155,9 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { "distro-location", "", "", - "Base URL used to download schemas, defaults and the distribution manifest. "+ - "It can either be a local path(eg: /path/to/fury/distribution) or "+ - "a remote URL(eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME)."+ + "Location where to download schemas, defaults and the distribution manifests from. "+ + "It can either be a local path (eg: /path/to/fury/distribution) or "+ + "a remote URL (eg: git::git@github.com:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME). "+ "Any format supported by hashicorp/go-getter can be used.", ) diff --git a/cmd/version.go b/cmd/version.go index 009dfe193..26f772eeb 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -20,7 +20,7 @@ func NewVersionCmd(versions map[string]string, tracker *analytics.Tracker) *cobr return &cobra.Command{ Use: "version", - Short: "Print the version number of furyctl", + Short: "Print the version number an build information of furyctl", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index e1c282e6d..0dd7b4377 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -302,7 +302,7 @@ func (*VpnConnector) checkExistingOpenVPN() (bool, int32, error) { } func (v *VpnConnector) startOpenVPN() error { - connectMsg := "Connecting to VPN" + connectMsg := "Connecting to the VPN" isRoot, err := osx.IsRoot() if err != nil { @@ -328,10 +328,10 @@ func (v *VpnConnector) startOpenVPN() error { } func (*VpnConnector) promptAutoConnect(pid int32) error { - logrus.Warnf("There is already a VPN connection process running with PID %d,"+ - " please confirm it is intended to be up before you continue.\n", pid) + logrus.Warnf("Found an openvpn process running with PID %d,"+ + " continuing will start another openvpn process and VPN connection in consequence.\n", pid) - logrus.Info("Press enter to continue or CTRL-C to abort...") + logrus.Info("Press ENTER to continue or CTRL-C to abort...") if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil { return fmt.Errorf("%w: %v", ErrReadStdin, err) @@ -373,7 +373,7 @@ func (v *VpnConnector) prompt() error { logrus.Info(connectMsg) - logrus.Info("Press enter when you are ready to continue...") + logrus.Info("Press ENTER when you are ready to continue...") if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil { return fmt.Errorf("%w: %v", ErrReadStdin, err) From 7baa87eb15da7869780f3e3ca0dc879dd9dcf069 Mon Sep 17 00:00:00 2001 From: Ramiro Algozino Date: Thu, 20 Apr 2023 16:22:07 +0200 Subject: [PATCH 270/383] chore: fix typo in version command help message Co-authored-by: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> --- cmd/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/version.go b/cmd/version.go index 26f772eeb..dcdb568ca 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -20,7 +20,7 @@ func NewVersionCmd(versions map[string]string, tracker *analytics.Tracker) *cobr return &cobra.Command{ Use: "version", - Short: "Print the version number an build information of furyctl", + Short: "Print the version number and build information of furyctl", PreRun: func(cmd *cobra.Command, _ []string) { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, From 9c70a5f3435be74e3ba9b85ccdd219494ca0add4 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 20 Apr 2023 18:10:14 +0200 Subject: [PATCH 271/383] chore: fixed download command in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c7452de8..8deb18cfd 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ You can find `furyctl` binaries on the [Releases page](https://github.com/sighup To download the latest release, run: ```console -wget -q "https://github.com/sighupio/furyctl/releases/download/v0.25.0-alpha.1/furyctl-$(uname -s)-amd64" -O /tmp/furyctl +curl -L "https://github.com/sighupio/furyctl/releases/download/v0.25.0-alpha.1/furyctl_$(uname -s)_x86_64.tar.gz" -o /tmp/furyctl.tar.gz && tar xfz /tmp/furyctl.tar.gz -C /tmp chmod +x /tmp/furyctl sudo mv /tmp/furyctl /usr/local/bin/furyctl ``` From 1c9716cd71ce638ba083f3a2650373bb3dc1b3bf Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 26 Apr 2023 13:48:35 +0200 Subject: [PATCH 272/383] feat: added git tool check --- internal/dependencies/tools/git.go | 59 +++++++++++++++ internal/dependencies/tools/git_test.go | 75 +++++++++++++++++++ internal/dependencies/tools/tool.go | 10 +++ internal/dependencies/tools/tool_test.go | 5 ++ internal/dependencies/tools/validator.go | 12 ++- internal/dependencies/tools/validator_test.go | 4 +- internal/tool/git/runner.go | 45 +++++++++++ internal/tool/git/runner_test.go | 58 ++++++++++++++ internal/tool/runner.go | 9 +++ 9 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 internal/dependencies/tools/git.go create mode 100644 internal/dependencies/tools/git_test.go create mode 100644 internal/tool/git/runner.go create mode 100644 internal/tool/git/runner_test.go diff --git a/internal/dependencies/tools/git.go b/internal/dependencies/tools/git.go new file mode 100644 index 000000000..8389dcc80 --- /dev/null +++ b/internal/dependencies/tools/git.go @@ -0,0 +1,59 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/tool/git" +) + +func NewGit(runner *git.Runner, version string) *Git { + return &Git{ + arch: "amd64", + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`git version .*`), + runner: runner, + splitFn: func(version string) []string { + return strings.Split(version, " ") + }, + trimFn: func(tokens []string) string { + return tokens[len(tokens)-1] + }, + }, + } +} + +type Git struct { + arch string + checker *checker + os string + version string +} + +func (*Git) SupportsDownload() bool { + return false +} + +func (*Git) SrcPath() string { + return "" +} + +func (*Git) Rename(_ string) error { + return nil +} + +func (a *Git) CheckBinVersion() error { + if err := a.checker.version(a.version); err != nil { + return fmt.Errorf("git: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/git_test.go b/internal/dependencies/tools/git_test.go new file mode 100644 index 000000000..d863658b0 --- /dev/null +++ b/internal/dependencies/tools/git_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/git" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Git_SupportsDownload(t *testing.T) { + a := tools.NewGit(newGitRunner(), "2.39.0") + + if a.SupportsDownload() != false { + t.Errorf("Git download must not be supported") + } +} + +func Test_Git_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "2.39.0", + }, + { + desc: "wrong version installed", + wantVersion: "2.39.1", + wantErr: true, + wantErrMsg: "installed = 2.39.0, expected = 2.39.1", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewGit(newGitRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newGitRunner() *git.Runner { + return git.NewRunner(execx.NewFakeExecutor(), git.Paths{ + Git: "git", + }) +} diff --git a/internal/dependencies/tools/tool.go b/internal/dependencies/tools/tool.go index 50c5e68c0..5a1ee066a 100644 --- a/internal/dependencies/tools/tool.go +++ b/internal/dependencies/tools/tool.go @@ -16,6 +16,7 @@ import ( "github.com/sighupio/furyctl/internal/tool/ansible" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/furyagent" + "github.com/sighupio/furyctl/internal/tool/git" "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/openvpn" @@ -89,6 +90,15 @@ func (f *Factory) Create(name, version string) Tool { return NewFuryagent(fa, version) } + if name == tool.Git { + g, ok := t.(*git.Runner) + if !ok { + panic(fmt.Sprintf("expected git.Runner, got %T", t)) + } + + return NewGit(g, version) + } + if name == tool.Kubectl { k, ok := t.(*kubectl.Runner) if !ok { diff --git a/internal/dependencies/tools/tool_test.go b/internal/dependencies/tools/tool_test.go index 34210fb8a..386c5160b 100644 --- a/internal/dependencies/tools/tool_test.go +++ b/internal/dependencies/tools/tool_test.go @@ -94,6 +94,11 @@ func TestHelperProcess(t *testing.T) { case "version": fmt.Fprintf(os.Stdout, "Furyagent version 0.3.0 - md5: b7d2b3dc7398ac6ce120a17d4fd439f2 - /opt/homebrew/bin/furyagent") } + case "git": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "git version 2.39.0\n") + } case "kubectl": switch subcmd { case "version": diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index d56862bd9..cfc29d57d 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -15,10 +15,7 @@ import ( execx "github.com/sighupio/furyctl/internal/x/exec" ) -var ( - ErrEmptyToolVersion = errors.New("empty tool version") - ErrWrongToolVersion = errors.New("wrong tool version") -) +var ErrWrongToolVersion = errors.New("wrong tool version") func NewValidator(executor execx.Executor, binPath, furyctlPath string, autoConnect bool) *Validator { return &Validator{ @@ -73,6 +70,13 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( } } + git := tv.toolFactory.Create(itool.Git, "*") + if err := git.CheckBinVersion(); err != nil { + errs = append(errs, err) + } else { + oks = append(oks, "git") + } + etv := apis.NewExtraToolsValidatorFactory(tv.executor, miniConf.APIVersion, miniConf.Kind, tv.autoConnect) if etv == nil { diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index b8d7e2a93..f5233867f 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -56,6 +56,7 @@ func Test_Validator_Validate(t *testing.T) { "terraform", "furyagent", "aws", + "git", }, }, { @@ -89,7 +90,7 @@ func Test_Validator_Validate(t *testing.T) { errors.New("kustomize: wrong tool version - installed = 3.9.4, expected = 3.5.3"), errors.New("terraform: wrong tool version - installed = 0.15.4, expected = 1.3.0"), }, - wantOks: []string{"aws"}, + wantOks: []string{"aws", "git"}, }, { desc: "openvpn is installed", @@ -125,6 +126,7 @@ func Test_Validator_Validate(t *testing.T) { "furyagent", "aws", "openvpn", + "git", }, }, } diff --git a/internal/tool/git/runner.go b/internal/tool/git/runner.go new file mode 100644 index 000000000..e81b37dd9 --- /dev/null +++ b/internal/tool/git/runner.go @@ -0,0 +1,45 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package git + +import ( + "fmt" + + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +type Paths struct { + Git string + WorkDir string +} + +type Runner struct { + executor execx.Executor + paths Paths +} + +func NewRunner(executor execx.Executor, paths Paths) *Runner { + return &Runner{ + executor: executor, + paths: paths, + } +} + +func (r *Runner) CmdPath() string { + return r.paths.Git +} + +func (r *Runner) Version() (string, error) { + out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Git, execx.CmdOptions{ + Args: []string{"version"}, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + })) + if err != nil { + return "", fmt.Errorf("error getting git version: %w", err) + } + + return out, nil +} diff --git a/internal/tool/git/runner_test.go b/internal/tool/git/runner_test.go new file mode 100644 index 000000000..3c108419b --- /dev/null +++ b/internal/tool/git/runner_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package git_test + +import ( + "fmt" + "os" + "testing" + + "github.com/sighupio/furyctl/internal/tool/git" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Runner_Version(t *testing.T) { + r := git.NewRunner(execx.NewFakeExecutor(), git.Paths{ + Git: "git", + WorkDir: os.TempDir(), + }) + + got, err := r.Version() + if err != nil { + t.Fatal(err) + } + + want := "git version 2.39.0" + + if got != want { + t.Errorf("expected version '%s', got '%s'", want, got) + } +} + +func TestHelperProcess(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "git": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "git version 2.39.0") + default: + fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) + } + default: + fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) + } + + os.Exit(0) +} diff --git a/internal/tool/runner.go b/internal/tool/runner.go index 202ed2b44..0370719ee 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -10,6 +10,7 @@ import ( "github.com/sighupio/furyctl/internal/tool/ansible" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/furyagent" + "github.com/sighupio/furyctl/internal/tool/git" "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/openvpn" @@ -21,6 +22,7 @@ const ( Ansible = "ansible" Awscli = "awscli" Furyagent = "furyagent" + Git = "git" Kubectl = "kubectl" Kustomize = "kustomize" Openvpn = "openvpn" @@ -70,6 +72,13 @@ func (rf *RunnerFactory) Create(name, version, workDir string) Runner { }) } + if name == Git { + return git.NewRunner(rf.executor, git.Paths{ + Git: "git", + WorkDir: workDir, + }) + } + if name == Kubectl { return kubectl.NewRunner(rf.executor, kubectl.Paths{ Kubectl: filepath.Join(rf.paths.Bin, name, version, name), From c87069cacace748633a73a552be967cfc3586e9d Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 26 Apr 2023 16:10:33 +0200 Subject: [PATCH 273/383] fix: validation needed before distro download --- cmd/create/cluster.go | 10 ++- cmd/create/config.go | 11 +++ cmd/delete/cluster.go | 12 +++- cmd/download/dependencies.go | 12 +++- cmd/dump/template.go | 12 ++++ cmd/validate/config.go | 12 ++++ cmd/validate/dependencies.go | 35 ++++++---- internal/dependencies/tools/validator.go | 23 +++++-- internal/dependencies/tools/validator_test.go | 69 ++++++++++++++++++- internal/dependencies/validate.go | 8 +++ 10 files changed, 177 insertions(+), 27 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index b44022dcc..62497b8da 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -123,10 +123,19 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { client := netx.NewGoGetterClient() executor := execx.NewStdExecutor() distrodl := distribution.NewDownloader(client) + depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) // Init packages. execx.NoTTY = flags.NoTTY + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } + // Download the distribution. logrus.Info("Downloading distribution...") res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) @@ -148,7 +157,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Init second half of collaborators. depsdl := dependencies.NewDownloader(client, basePath, flags.BinPath) - depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) // Validate the furyctl.yaml file. logrus.Info("Validating configuration file...") diff --git a/cmd/create/config.go b/cmd/create/config.go index 8eedb8a91..ccb75ecdc 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -17,6 +17,7 @@ import ( "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/dependencies" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" cobrax "github.com/sighupio/furyctl/internal/x/cobra" @@ -102,6 +103,8 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { // Init collaborators. distrodl := distribution.NewDownloader(netx.NewGoGetterClient()) + executor := execx.NewStdExecutor() + depsvl := dependencies.NewValidator(executor, "", "", false) // Init packages. execx.Debug = debug @@ -122,6 +125,14 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { ) } + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } + // Download the distribution. logrus.Info("Downloading distribution...") res, err := distrodl.DoDownload(distroLocation, minimalConf) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index c7e9db952..b832670d5 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -104,10 +104,19 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { client := netx.NewGoGetterClient() executor := execx.NewStdExecutor() distrodl := distribution.NewDownloader(client) + depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) execx.Debug = flags.Debug execx.NoTTY = flags.NoTTY + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } + // Download the distribution. logrus.Info("Downloading distribution...") res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) @@ -122,9 +131,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) - // Init second half of collaborators. - depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) - // Validate the dependencies. logrus.Info("Validating dependencies...") if err := depsvl.Validate(res); err != nil { diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 39a79cc16..4f127bffa 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -18,6 +18,7 @@ import ( "github.com/sighupio/furyctl/internal/dependencies" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" netx "github.com/sighupio/furyctl/internal/x/net" ) @@ -63,9 +64,18 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { logrus.Info("Downloading dependencies...") client := netx.NewGoGetterClient() - + executor := execx.NewStdExecutor() + depsvl := dependencies.NewValidator(executor, binPath, furyctlPath, false) distrodl := distribution.NewDownloader(client) + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } + dres, err := distrodl.Download(distroLocation, furyctlPath) cmdEvent.AddClusterDetails(analytics.ClusterDetails{ KFDVersion: dres.DistroManifest.Version, diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 72e4bc167..de9e20e7a 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -14,8 +14,10 @@ import ( "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/dependencies" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" netx "github.com/sighupio/furyctl/internal/x/net" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) @@ -53,8 +55,18 @@ The generated folder will be created starting from a provided templates folder a // Init collaborators. client := netx.NewGoGetterClient() + executor := execx.NewStdExecutor() + depsvl := dependencies.NewValidator(executor, "", flags.FuryctlPath, false) distrodl := distribution.NewDownloader(client) + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } + // Download the distribution. logrus.Info("Downloading distribution...") res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 982e16c26..84eb5f31b 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -14,8 +14,10 @@ import ( "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" "github.com/sighupio/furyctl/internal/config" + "github.com/sighupio/furyctl/internal/dependencies" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" netx "github.com/sighupio/furyctl/internal/x/net" ) @@ -44,8 +46,18 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("%w: distro-location", ErrParsingFlag) } + executor := execx.NewStdExecutor() + depsvl := dependencies.NewValidator(executor, "", furyctlPath, false) dloader := distribution.NewDownloader(netx.NewGoGetterClient()) + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } + // Download the distribution. logrus.Info("Downloading distribution...") res, err := dloader.Download(distroLocation, furyctlPath) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 5ccad64b2..5693bb623 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -14,6 +14,7 @@ import ( "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" + "github.com/sighupio/furyctl/internal/dependencies" "github.com/sighupio/furyctl/internal/dependencies/envvars" "github.com/sighupio/furyctl/internal/dependencies/tools" "github.com/sighupio/furyctl/internal/dependencies/toolsconf" @@ -45,7 +46,27 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("%w: distro-location", ErrParsingFlag) } + binPath := cobrax.Flag[string](cmd, "bin-path").(string) //nolint:errcheck,forcetypeassert // optional flag + if binPath == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("error while getting user home directory: %w", err) + } + + binPath = filepath.Join(homeDir, ".furyctl", "bin") + } + dloader := distribution.NewDownloader(netx.NewGoGetterClient()) + executor := execx.NewStdExecutor() + depsvl := dependencies.NewValidator(executor, "", furyctlPath, false) + + // Validate base requirements. + if err := depsvl.ValidateBaseReqs(); err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + + return fmt.Errorf("error while validating requirements: %w", err) + } // Download the distribution. logrus.Info("Downloading distribution...") @@ -61,21 +82,11 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { KFDVersion: dres.DistroManifest.Version, }) - binPath := cobrax.Flag[string](cmd, "bin-path").(string) //nolint:errcheck,forcetypeassert // optional flag - if binPath == "" { - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("error while getting user home directory: %w", err) - } - - binPath = filepath.Join(homeDir, ".furyctl", "bin") - } - - toolsValidator := tools.NewValidator(execx.NewStdExecutor(), binPath, furyctlPath, false) + toolsValidator := tools.NewValidator(executor, binPath, furyctlPath, false) envVarsValidator := envvars.NewValidator() - toolsConfigValidator := toolsconf.NewValidator(execx.NewStdExecutor()) + toolsConfigValidator := toolsconf.NewValidator(executor) errs := make([]error, 0) diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index cfc29d57d..11514ab2e 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -35,6 +35,22 @@ type Validator struct { autoConnect bool } +func (tv *Validator) ValidateBaseReqs() ([]string, []error) { + var ( + oks []string + errs []error + ) + + git := tv.toolFactory.Create(itool.Git, "*") + if err := git.CheckBinVersion(); err != nil { + errs = append(errs, err) + } else { + oks = append(oks, "git") + } + + return oks, errs +} + func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ([]string, []error) { var ( oks []string @@ -70,13 +86,6 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( } } - git := tv.toolFactory.Create(itool.Git, "*") - if err := git.CheckBinVersion(); err != nil { - errs = append(errs, err) - } else { - oks = append(oks, "git") - } - etv := apis.NewExtraToolsValidatorFactory(tv.executor, miniConf.APIVersion, miniConf.Kind, tv.autoConnect) if etv == nil { diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index f5233867f..357dd3294 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -56,7 +56,6 @@ func Test_Validator_Validate(t *testing.T) { "terraform", "furyagent", "aws", - "git", }, }, { @@ -90,7 +89,7 @@ func Test_Validator_Validate(t *testing.T) { errors.New("kustomize: wrong tool version - installed = 3.9.4, expected = 3.5.3"), errors.New("terraform: wrong tool version - installed = 0.15.4, expected = 1.3.0"), }, - wantOks: []string{"aws", "git"}, + wantOks: []string{"aws"}, }, { desc: "openvpn is installed", @@ -126,7 +125,6 @@ func Test_Validator_Validate(t *testing.T) { "furyagent", "aws", "openvpn", - "git", }, }, } @@ -180,3 +178,68 @@ func Test_Validator_Validate(t *testing.T) { }) } } + +func TestValidator_ValidateBaseReqs(t *testing.T) { + testCases := []struct { + desc string + wantOks []string + wantErrs []error + }{ + { + desc: "all base requirements are met", + wantOks: []string{ + "git", + }, + }, + } + + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + furyctlPath := path.Join("test_data", "furyctl.yaml") + + v := tools.NewValidator(execx.NewFakeExecutor(), "test_data", furyctlPath, false) + + oks, errs := v.ValidateBaseReqs() + + if len(oks) != len(tC.wantOks) { + t.Errorf("Expected %d oks, got %d - %v", len(tC.wantOks), len(oks), oks) + } + + if len(errs) != len(tC.wantErrs) { + t.Errorf("Expected %d errors, got %d - %v", len(tC.wantErrs), len(errs), errs) + } + + for _, ok := range oks { + found := false + for _, wantOk := range tC.wantOks { + if ok == wantOk { + found = true + + break + } + } + + if !found { + t.Errorf("Unexpected ok: %s", ok) + } + } + + for _, err := range errs { + found := false + for _, wantErr := range tC.wantErrs { + if strings.Trim(err.Error(), "\n") == strings.Trim(wantErr.Error(), "\n") { + found = true + + break + } + } + + if !found { + t.Errorf("Unexpected error: %s", err) + } + } + }) + } +} diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index 0c1f07a26..89bdf0e23 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -35,6 +35,14 @@ type Validator struct { infraValidator *toolsconf.Validator } +func (v *Validator) ValidateBaseReqs() error { + if _, errs := v.toolsValidator.ValidateBaseReqs(); len(errs) > 0 { + return fmt.Errorf("%w: %v", errValidatingTools, errs) + } + + return nil +} + func (v *Validator) Validate(res distribution.DownloadResult) error { if _, errs := v.toolsValidator.Validate( res.DistroManifest, From 769cf205a3985cccebbbb973b23c4263e5be695f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 27 Apr 2023 12:47:21 +0200 Subject: [PATCH 274/383] feat: added https flag in every cmd --- cmd/create/cluster.go | 11 +++++++++-- cmd/create/config.go | 7 ++++++- cmd/delete/cluster.go | 9 ++++++++- cmd/download/dependencies.go | 13 +++++++++---- cmd/dump/template.go | 9 ++++++++- cmd/legacy/vendor.go | 9 --------- cmd/root.go | 9 +++++++++ cmd/validate/config.go | 7 ++++++- cmd/validate/dependencies.go | 7 ++++++- internal/dependencies/download.go | 27 +++++++++++++++++++------- internal/distribution/download.go | 18 ++++++++++++++--- internal/distribution/download_test.go | 2 +- 12 files changed, 97 insertions(+), 31 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 62497b8da..b6cf2e78e 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -45,6 +45,7 @@ type ClusterCmdFlags struct { SkipDepsDownload bool SkipDepsValidation bool NoTTY bool + HTTPS bool Kubeconfig string } @@ -122,7 +123,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Init first half of collaborators. client := netx.NewGoGetterClient() executor := execx.NewStdExecutor() - distrodl := distribution.NewDownloader(client) + distrodl := distribution.NewDownloader(client, flags.HTTPS) depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) // Init packages. @@ -156,7 +157,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) // Init second half of collaborators. - depsdl := dependencies.NewDownloader(client, basePath, flags.BinPath) + depsdl := dependencies.NewDownloader(client, basePath, flags.BinPath, flags.HTTPS) // Validate the furyctl.yaml file. logrus.Info("Validating configuration file...") @@ -326,6 +327,11 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "kubeconfig") } + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "https") + } + return ClusterCmdFlags{ Debug: debug, FuryctlPath: furyctlPath, @@ -340,6 +346,7 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm SkipDepsValidation: skipDepsValidation, NoTTY: noTTY, Kubeconfig: kubeconfig, + HTTPS: https, }, nil } diff --git a/cmd/create/config.go b/cmd/create/config.go index ccb75ecdc..c2ecabe80 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -79,6 +79,11 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("%w: name", ErrParsingFlag) } + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: https", ErrParsingFlag) + } + minimalConf := distroConfig.Furyctl{ APIVersion: apiVersion, Kind: kind, @@ -102,7 +107,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { } // Init collaborators. - distrodl := distribution.NewDownloader(netx.NewGoGetterClient()) + distrodl := distribution.NewDownloader(netx.NewGoGetterClient(), https) executor := execx.NewStdExecutor() depsvl := dependencies.NewValidator(executor, "", "", false) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index b832670d5..904178dba 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -42,6 +42,7 @@ type ClusterCmdFlags struct { VpnAutoConnect bool DryRun bool NoTTY bool + HTTPS bool Kubeconfig string } @@ -103,7 +104,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Init first half of collaborators. client := netx.NewGoGetterClient() executor := execx.NewStdExecutor() - distrodl := distribution.NewDownloader(client) + distrodl := distribution.NewDownloader(client, flags.HTTPS) depsvl := dependencies.NewValidator(executor, flags.BinPath, flags.FuryctlPath, flags.VpnAutoConnect) execx.Debug = flags.Debug @@ -330,6 +331,11 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm return ClusterCmdFlags{}, fmt.Errorf("%w: force", ErrParsingFlag) } + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "https") + } + return ClusterCmdFlags{ Debug: debug, FuryctlPath: furyctlPath, @@ -342,6 +348,7 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm Force: force, NoTTY: noTTY, Kubeconfig: kubeconfig, + HTTPS: https, }, nil } diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 4f127bffa..15fa74a97 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -47,6 +47,11 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("%w: distro-location", ErrParsingFlag) } + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: https", ErrParsingFlag) + } + binPath := cmdutil.StringFlagOptional(cmd, "bin-path") homeDir, err := os.UserHomeDir() @@ -61,12 +66,10 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { binPath = filepath.Join(homeDir, ".furyctl", "bin") } - logrus.Info("Downloading dependencies...") - client := netx.NewGoGetterClient() executor := execx.NewStdExecutor() depsvl := dependencies.NewValidator(executor, binPath, furyctlPath, false) - distrodl := distribution.NewDownloader(client) + distrodl := distribution.NewDownloader(client, https) // Validate base requirements. if err := depsvl.ValidateBaseReqs(); err != nil { @@ -90,7 +93,9 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { basePath := filepath.Join(homeDir, ".furyctl", dres.MinimalConf.Metadata.Name) - depsdl := dependencies.NewDownloader(client, basePath, binPath) + depsdl := dependencies.NewDownloader(client, basePath, binPath, https) + + logrus.Info("Downloading dependencies...") errs, uts := depsdl.DownloadAll(dres.DistroManifest) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index de9e20e7a..0e44e6eeb 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -28,6 +28,7 @@ type TemplateCmdFlags struct { DryRun bool NoOverwrite bool SkipValidation bool + HTTPS bool OutDir string FuryctlPath string DistroLocation string @@ -57,7 +58,7 @@ The generated folder will be created starting from a provided templates folder a client := netx.NewGoGetterClient() executor := execx.NewStdExecutor() depsvl := dependencies.NewValidator(executor, "", flags.FuryctlPath, false) - distrodl := distribution.NewDownloader(client) + distrodl := distribution.NewDownloader(client, flags.HTTPS) // Validate base requirements. if err := depsvl.ValidateBaseReqs(); err != nil { @@ -205,6 +206,11 @@ func getDumpTemplateCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmd return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "config") } + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return TemplateCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "https") + } + return TemplateCmdFlags{ DryRun: dryRun, NoOverwrite: noOverwrite, @@ -212,5 +218,6 @@ func getDumpTemplateCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmd OutDir: outDir, DistroLocation: distroLocation, FuryctlPath: furyctlPath, + HTTPS: https, }, nil } diff --git a/cmd/legacy/vendor.go b/cmd/legacy/vendor.go index 7de7c5004..9b2eb7468 100644 --- a/cmd/legacy/vendor.go +++ b/cmd/legacy/vendor.go @@ -94,15 +94,6 @@ func NewVendorCmd(tracker *analytics.Tracker) *cobra.Command { "Path to the Furyfile.yml file", ) - cmd.Flags().BoolP( - "https", - "H", - false, - "download using HTTPS instead of SSH protocol. Use when SSH traffic is being blocked or when SSH "+ - "client has not been configured\nset the GITHUB_TOKEN environment variable with your token to use "+ - "authentication while downloading, for example for private repositories", - ) - cmd.Flags().StringP( "prefix", "P", diff --git a/cmd/root.go b/cmd/root.go index 529281cad..4bffca666 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -178,6 +178,15 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber "Path to the log file or set to 'stdout' to log to standard output (default: ~/.furyctl/furyctl.log)", ) + rootCmd.PersistentFlags().BoolP( + "https", + "H", + false, + "download using HTTPS instead of SSH protocol. Use when SSH traffic is being blocked or when SSH "+ + "client has not been configured\nset the GITHUB_TOKEN environment variable with your token to use "+ + "authentication while downloading, for example for private repositories", + ) + rootCmd.AddCommand(NewCompletionCmd(tracker)) rootCmd.AddCommand(NewCreateCommand(tracker)) rootCmd.AddCommand(NewDownloadCmd(tracker)) diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 84eb5f31b..8de20e0c4 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -46,9 +46,14 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("%w: distro-location", ErrParsingFlag) } + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: https", ErrParsingFlag) + } + executor := execx.NewStdExecutor() depsvl := dependencies.NewValidator(executor, "", furyctlPath, false) - dloader := distribution.NewDownloader(netx.NewGoGetterClient()) + dloader := distribution.NewDownloader(netx.NewGoGetterClient(), https) // Validate base requirements. if err := depsvl.ValidateBaseReqs(); err != nil { diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 5693bb623..64e992ecd 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -56,7 +56,12 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { binPath = filepath.Join(homeDir, ".furyctl", "bin") } - dloader := distribution.NewDownloader(netx.NewGoGetterClient()) + https, err := cmdutil.BoolFlag(cmd, "https", tracker, cmdEvent) + if err != nil { + return fmt.Errorf("%w: https", ErrParsingFlag) + } + + dloader := distribution.NewDownloader(netx.NewGoGetterClient(), https) executor := execx.NewStdExecutor() depsvl := dependencies.NewValidator(executor, "", furyctlPath, false) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 5e5a4b379..9d6dc6e74 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -25,6 +25,11 @@ import ( netx "github.com/sighupio/furyctl/internal/x/net" ) +const ( + GithubSSHRepoPrefix = "git@github.com:sighupio" + GithubHTTPSRepoPrefix = "https://github.com/sighupio" +) + var ( ErrDownloadingModule = errors.New("error downloading module") ErrModuleHasNoVersion = errors.New("module has no version") @@ -32,7 +37,7 @@ var ( ErrModuleNotFound = errors.New("module not found") ) -func NewDownloader(client netx.Client, basePath, binPath string) *Downloader { +func NewDownloader(client netx.Client, basePath, binPath string, https bool) *Downloader { return &Downloader{ client: client, basePath: basePath, @@ -40,6 +45,7 @@ func NewDownloader(client netx.Client, basePath, binPath string) *Downloader { toolFactory: tools.NewFactory(execx.NewStdExecutor(), tools.FactoryPaths{ Bin: filepath.Join(basePath, "vendor", "bin"), }), + HTTPS: https, } } @@ -48,6 +54,7 @@ type Downloader struct { toolFactory *tools.Factory basePath string binPath string + HTTPS bool } func (dd *Downloader) DownloadAll(kfd config.KFD) ([]error, []string) { @@ -66,11 +73,17 @@ func (dd *Downloader) DownloadAll(kfd config.KFD) ([]error, []string) { } } - if err := dd.DownloadModules(kfd.Modules); err != nil { + gitPrefix := GithubSSHRepoPrefix + + if dd.HTTPS { + gitPrefix = GithubHTTPSRepoPrefix + } + + if err := dd.DownloadModules(kfd.Modules, gitPrefix); err != nil { errs = append(errs, err) } - if err := dd.DownloadInstallers(kfd.Kubernetes); err != nil { + if err := dd.DownloadInstallers(kfd.Kubernetes, gitPrefix); err != nil { errs = append(errs, err) } @@ -82,7 +95,7 @@ func (dd *Downloader) DownloadAll(kfd config.KFD) ([]error, []string) { return errs, ut } -func (dd *Downloader) DownloadModules(modules config.KFDModules) error { +func (dd *Downloader) DownloadModules(modules config.KFDModules, gitPrefix string) error { oldPrefix := "kubernetes-fury" newPrefix := "fury-kubernetes" @@ -106,7 +119,7 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { dst := filepath.Join(dd.basePath, "vendor", "modules", name) for _, prefix := range []string{oldPrefix, newPrefix} { - src := fmt.Sprintf("git::git@github.com:sighupio/%s-%s?ref=%s&depth=1", prefix, name, version) + src := fmt.Sprintf("git::%s/%s-%s?ref=%s&depth=1", gitPrefix, prefix, name, version) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, createURL(prefix, name, version), nil) if err != nil { @@ -160,7 +173,7 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules) error { return nil } -func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes) error { +func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes, gitPrefix string) error { insts := reflect.ValueOf(installers) for i := 0; i < insts.NumField(); i++ { @@ -175,7 +188,7 @@ func (dd *Downloader) DownloadInstallers(installers config.KFDKubernetes) error version := v.Installer - src := fmt.Sprintf("git::git@github.com:sighupio/fury-%s-installer?ref=%s&depth=1", name, version) + src := fmt.Sprintf("git::%s/fury-%s-installer?ref=%s&depth=1", gitPrefix, name, version) if err := dd.client.Download(src, dst); err != nil { return fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, src, err) diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 057c690bc..6688e7c05 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -19,7 +19,11 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const DefaultBaseURL = "git::git@github.com:sighupio/fury-distribution?ref=%s&depth=1" +const ( + DefaultBaseURL = "git::%s/fury-distribution?ref=%s&depth=1" + GithubSSHRepoPrefix = "git@github.com:sighupio" + GithubHTTPSRepoPrefix = "https://github.com/sighupio" +) var ( ErrChangingFilePermissions = errors.New("error changing file permissions") @@ -42,16 +46,18 @@ type DownloadResult struct { DistroManifest config.KFD } -func NewDownloader(client netx.Client) *Downloader { +func NewDownloader(client netx.Client, https bool) *Downloader { return &Downloader{ client: client, validate: config.NewValidator(), + HTTPS: https, } } type Downloader struct { client netx.Client validate *validator.Validate + HTTPS bool } func (d *Downloader) Download( @@ -72,8 +78,14 @@ func (d *Downloader) DoDownload( ) (DownloadResult, error) { url := distroLocation + gitPrefix := GithubSSHRepoPrefix + + if d.HTTPS { + gitPrefix = GithubHTTPSRepoPrefix + } + if distroLocation == "" { - url = fmt.Sprintf(DefaultBaseURL, minimalConf.Spec.DistributionVersion) + url = fmt.Sprintf(DefaultBaseURL, gitPrefix, minimalConf.Spec.DistributionVersion) } if strings.HasPrefix(url, ".") { diff --git a/internal/distribution/download_test.go b/internal/distribution/download_test.go index 60e2711f0..8d5cc1938 100644 --- a/internal/distribution/download_test.go +++ b/internal/distribution/download_test.go @@ -51,7 +51,7 @@ func Test_Downloader_Download(t *testing.T) { t.Fatal(err) } - d := distribution.NewDownloader(netx.NewGoGetterClient()) + d := distribution.NewDownloader(netx.NewGoGetterClient(), false) res, err := d.Download( absDistroPath, From ee568f39f400329a865246bdeae24735187ae58b Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 4 May 2023 10:20:37 +0200 Subject: [PATCH 275/383] chore: up golang version --- .drone.yml | 12 ++++++------ Makefile | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.drone.yml b/.drone.yml index ef6119a4b..471908af0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ name: main steps: - name: prepare - image: quay.io/sighup/golang:1.19.6 + image: quay.io/sighup/golang:1.20.4 depends_on: - clone pull: always @@ -17,7 +17,7 @@ steps: - go mod download - name: license - image: quay.io/sighup/golang:1.19.6 + image: quay.io/sighup/golang:1.20.4 depends_on: - clone pull: always @@ -30,7 +30,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: lint - image: quay.io/sighup/golang:1.19.6 + image: quay.io/sighup/golang:1.20.4 depends_on: - prepare pull: always @@ -43,7 +43,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-unit - image: quay.io/sighup/golang:1.19.6 + image: quay.io/sighup/golang:1.20.4 depends_on: - prepare pull: always @@ -56,7 +56,7 @@ steps: GOTMPDIR: /drone/src/.go/tmp - name: test-integration - image: quay.io/sighup/golang:1.19.6 + image: quay.io/sighup/golang:1.20.4 depends_on: - prepare commands: @@ -71,7 +71,7 @@ steps: from_secret: NETRC_FILE - name: test-e2e - image: quay.io/sighup/golang:1.19.6 + image: quay.io/sighup/golang:1.20.4 depends_on: - prepare commands: diff --git a/Makefile b/Makefile index 5d46f89f3..6414758f5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ _PROJECT_DIRECTORY = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) -_GOLANG_IMAGE = golang:1.19.6 +_GOLANG_IMAGE = golang:1.20.4 _PROJECTNAME = furyctl _GOARCH = "amd64" _BIN_OPEN = "open" From 7074e4a90ee2feb204a6b066778f761565246ff8 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 4 May 2023 10:39:43 +0200 Subject: [PATCH 276/383] fix: linting + tests --- .drone.yml | 2 +- .rules/.golangci.yml | 5 +++++ internal/analytics/event.go | 14 +++++++------- internal/analytics/tracker.go | 2 +- .../apis/kfd/v1alpha2/eks/create/kubernetes.go | 2 +- internal/template/model_test.go | 4 ++-- internal/template/node.go | 4 ++-- internal/x/kube/secret.go | 12 ++++++------ internal/x/kube/secret_test.go | 12 ++++++------ .../cluster/infrastructure/data/furyctl.yaml | 2 +- .../create/cluster/kubernetes/data/furyctl.yaml | 10 +++++----- .../e2e/validate/dependencies/correct/furyctl.yaml | 2 +- 12 files changed, 38 insertions(+), 33 deletions(-) diff --git a/.drone.yml b/.drone.yml index 471908af0..052f12d7f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -96,7 +96,7 @@ steps: - chmod 600 /root/.ssh/known_hosts - ssh-keyscan -H github.com > /etc/ssh/ssh_known_hosts 2> /dev/null # Create required s3 bucket if it doesn't exist - - (test ! $(aws s3api get-bucket-location --bucket furyctl-e2e-deps-test --output text --no-cli-pager 2>/dev/null | grep "$${AWS_DEFAULT_REGION}")) && aws s3 mb s3://furyctl-e2e-deps-test --region $${AWS_DEFAULT_REGION} + - (test ! $(aws s3api get-bucket-location --bucket furyctl-e2e-deps-test-ci --output text --no-cli-pager 2>/dev/null | grep "$${AWS_DEFAULT_REGION}")) && aws s3 mb s3://furyctl-e2e-deps-test-ci --region $${AWS_DEFAULT_REGION} # Run tests - make test-e2e # Cleanup diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml index 7038229d7..b97f11daf 100644 --- a/.rules/.golangci.yml +++ b/.rules/.golangci.yml @@ -154,6 +154,11 @@ linters-settings: severity: warning disabled: false arguments: [210] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#comment-spacings + - name: comment-spacings + severity: warning + disabled: false + arguments: ["nolint"] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming - name: confusing-naming severity: warning diff --git a/internal/analytics/event.go b/internal/analytics/event.go index cb40fa803..bca1ab732 100644 --- a/internal/analytics/event.go +++ b/internal/analytics/event.go @@ -5,7 +5,7 @@ package analytics type Event interface { - Properties() map[string]interface{} + Properties() map[string]any AddErrorMessage(error) AddSuccessMessage(string) AddClusterDetails(c ClusterDetails) @@ -16,7 +16,7 @@ type Event interface { func NewCommandEvent(name string) Event { return CommandEvent{ name: name, - properties: make(map[string]interface{}), + properties: make(map[string]any), } } @@ -38,7 +38,7 @@ func (c CommandEvent) AddExitCode(e int) { c.properties["exitCode"] = e } -func (c CommandEvent) Properties() map[string]interface{} { +func (c CommandEvent) Properties() map[string]any { return c.properties } @@ -50,7 +50,7 @@ func (c CommandEvent) Name() string { func NewStopEvent() Event { return StopEvent{ name: "stop", - properties: make(map[string]interface{}), + properties: make(map[string]any), } } @@ -70,7 +70,7 @@ func (g StopEvent) AddExitCode(e int) { g.properties["exitCode"] = e } -func (g StopEvent) Properties() map[string]interface{} { +func (g StopEvent) Properties() map[string]any { return g.properties } @@ -81,7 +81,7 @@ func (g StopEvent) Name() string { // StopEvent is a special event used to close the events processing. type StopEvent struct { name string - properties map[string]interface{} + properties map[string]any } type CommandEvent struct { @@ -89,7 +89,7 @@ type CommandEvent struct { properties } -type properties map[string]interface{} +type properties map[string]any type ClusterDetails struct { Phase string diff --git a/internal/analytics/tracker.go b/internal/analytics/tracker.go index 1ab9de25a..5ba612c59 100644 --- a/internal/analytics/tracker.go +++ b/internal/analytics/tracker.go @@ -167,7 +167,7 @@ func generateMachineID() string { return mid } -func appendMachineInfo(src map[string]string, dst map[string]interface{}) map[string]interface{} { +func appendMachineInfo(src map[string]string, dst map[string]any) map[string]any { for k, v := range src { dst[k] = v } diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 531c94455..368c47b5c 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -363,7 +363,7 @@ func (k *Kubernetes) createTfVars() error { return errPvtSubnetNotFound } - s, ok := infraOut.Outputs["private_subnets"].Value.([]interface{}) + s, ok := infraOut.Outputs["private_subnets"].Value.([]any) if !ok { return errPvtSubnetFromOut } diff --git a/internal/template/model_test.go b/internal/template/model_test.go index 9a13c928a..183e1371d 100644 --- a/internal/template/model_test.go +++ b/internal/template/model_test.go @@ -17,8 +17,8 @@ import ( ) func TestNewTemplateModel(t *testing.T) { - conf := map[string]interface{}{ - "data": map[string]interface{}{ + conf := map[string]any{ + "data": map[string]any{ "meta": map[string]string{ "name": "test", }, diff --git a/internal/template/node.go b/internal/template/node.go index 06d809312..ba116584f 100644 --- a/internal/template/node.go +++ b/internal/template/node.go @@ -37,9 +37,9 @@ func (f *Node) FromNodeList(nodes []parse.Node) []string { return slices.Uniq(f.Fields) } -func mapToAliasInterface(n parse.Node) interface{} { +func mapToAliasInterface(n parse.Node) any { // MapParseNodeToAlias is a map of parse.Node to its alias. - mapParseNodeToAlias := map[parse.NodeType]interface{}{ + mapParseNodeToAlias := map[parse.NodeType]any{ parse.NodeList: &ListNode{}, parse.NodeRange: &RangeNode{}, parse.NodePipe: &PipeNode{}, diff --git a/internal/x/kube/secret.go b/internal/x/kube/secret.go index 8ab04654c..1220970a2 100644 --- a/internal/x/kube/secret.go +++ b/internal/x/kube/secret.go @@ -13,15 +13,15 @@ import ( func CreateSecret(data []byte, name, namespace string) ([]byte, error) { secret := struct { - APIVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Metadata map[string]interface{} `yaml:"metadata"` - Type string `yaml:"type"` - Data map[string]string `yaml:"data"` + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata map[string]any `yaml:"metadata"` + Type string `yaml:"type"` + Data map[string]string `yaml:"data"` }{ APIVersion: "v1", Kind: "Secret", - Metadata: map[string]interface{}{ + Metadata: map[string]any{ "name": name, "namespace": namespace, }, diff --git a/internal/x/kube/secret_test.go b/internal/x/kube/secret_test.go index 9b9f5bce2..10d8fc7d8 100644 --- a/internal/x/kube/secret_test.go +++ b/internal/x/kube/secret_test.go @@ -19,15 +19,15 @@ func TestCreateSecret(t *testing.T) { config := "dGVzdA==" secret := struct { - APIVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Metadata map[string]interface{} `yaml:"metadata"` - Type string `yaml:"type"` - Data map[string]string `yaml:"data"` + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata map[string]any `yaml:"metadata"` + Type string `yaml:"type"` + Data map[string]string `yaml:"data"` }{ APIVersion: "v1", Kind: "Secret", - Metadata: map[string]interface{}{ + Metadata: map[string]any{ "name": name, "namespace": namespace, }, diff --git a/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml index 37a1786ce..034bf6477 100644 --- a/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml @@ -12,7 +12,7 @@ spec: terraform: state: s3: - bucketName: furyctl-test + bucketName: furyctl-e2e-deps-test-ci keyPrefix: furyctl/ region: eu-west-1 region: eu-west-1 diff --git a/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml b/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml index da2f97489..b72ccf291 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml @@ -12,7 +12,7 @@ spec: terraform: state: s3: - bucketName: furyctl-test + bucketName: furyctl-e2e-deps-test-ci keyPrefix: furyctl/ region: eu-west-1 region: eu-west-1 @@ -22,11 +22,11 @@ spec: kubernetes: nodeAllowedSshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxA+4CXYSmsN1m5DSsRzVRTegbvCh1gh7YObg0drj4+ alessio.pragliola@sighup.io" nodePoolsLaunchKind: "launch_templates" - vpcId: "vpc-0040e94dde71882f8" + vpcId: "vpc-04a74eb4c1eab8b2d" subnetIds: - - "subnet-08ebb79bd8ef8e08e" - - "subnet-014ca0081b369daf7" - - "subnet-0071d41306f6e810a" + - "subnet-00699eaca4b61fb9c" + - "subnet-02e860f39b603c4ff" + - "subnet-0cdc8ec151dc56188" apiServer: privateAccess: true publicAccess: false diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index d1e70f21e..e9cc1d2fb 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -15,7 +15,7 @@ spec: terraform: state: s3: - bucketName: furyctl-e2e-deps-test + bucketName: furyctl-e2e-deps-test-ci # changed from key, because each terraform project state will be placed in the directory defined by the prefix keyPrefix: furyctl/ region: eu-west-1 From cbf608f32833e86aa2a20bc90af41bdd0f0b623f Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 4 May 2023 10:47:28 +0200 Subject: [PATCH 277/383] fix: upped go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 324a2a4fc..fe7e890f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sighupio/furyctl -go 1.19 +go 1.20 require ( github.com/Al-Pragliola/go-version v1.6.2 From 9272b6f512ea7a3167bd781fe4ac0eaa82eefd6e Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 4 May 2023 10:50:14 +0200 Subject: [PATCH 278/383] chore: upped go version missing --- .rules/.golangci.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml index b97f11daf..799263e8d 100644 --- a/.rules/.golangci.yml +++ b/.rules/.golangci.yml @@ -85,7 +85,7 @@ linters-settings: capital: true period: true gofumpt: - lang-version: "1.19" + lang-version: "1.20" extra-rules: true goimports: local-prefixes: github.com/sighupio diff --git a/README.md b/README.md index 8deb18cfd..b76b2f314 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Check that everything is working correctly with `furyctl version`: $ furyctl version buildTime: 2023-01-13T09:50:15Z gitCommit: 349c14a06dd6163b308e4e8baa47ec9cc59712e1 -goVersion: go1.19 +goVersion: go1.20 osArch: amd64 version: 0.25.0-alpha.1 ``` --> @@ -93,7 +93,7 @@ version: 0.25.0-alpha.1 Prerequisites: - `make >= 4.1` -- `go >= 1.19` +- `go >= 1.20` - `goreleaser >= v1.15` > You can install `goreleaser` with the following command once you have Go in your system: From c0416454bd97612a7f395778b1c9ddd3aa008975 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 4 May 2023 11:51:19 +0200 Subject: [PATCH 279/383] fix: replaced hard-coded bucketName to env var TERRAFORM_TF_STATES_BUCKET_NAME --- .drone.yml | 4 +++- .../cluster/infrastructure/data/furyctl.yaml | 2 +- .../create/cluster/kubernetes/data/furyctl.yaml | 2 +- .../validate/dependencies/correct/furyctl.yaml | 2 +- test/e2e/furyctl_test.go | 16 ++++++++++++++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.drone.yml b/.drone.yml index 052f12d7f..25f2c5516 100644 --- a/.drone.yml +++ b/.drone.yml @@ -96,7 +96,7 @@ steps: - chmod 600 /root/.ssh/known_hosts - ssh-keyscan -H github.com > /etc/ssh/ssh_known_hosts 2> /dev/null # Create required s3 bucket if it doesn't exist - - (test ! $(aws s3api get-bucket-location --bucket furyctl-e2e-deps-test-ci --output text --no-cli-pager 2>/dev/null | grep "$${AWS_DEFAULT_REGION}")) && aws s3 mb s3://furyctl-e2e-deps-test-ci --region $${AWS_DEFAULT_REGION} + - (test ! $(aws s3api get-bucket-location --bucket $${TERRAFORM_TF_STATES_BUCKET_NAME} --output text --no-cli-pager 2>/dev/null | grep "$${AWS_DEFAULT_REGION}")) && aws s3 mb s3://$${TERRAFORM_TF_STATES_BUCKET_NAME} --region $${AWS_DEFAULT_REGION} # Run tests - make test-e2e # Cleanup @@ -121,6 +121,8 @@ steps: from_secret: NETRC_FILE GITHUB_SSH: from_secret: GITHUB_SSH + TERRAFORM_TF_STATES_BUCKET_NAME: + from_secret: TERRAFORM_TF_STATES_BUCKET_NAME - name: build image: ghcr.io/goreleaser/goreleaser:v1.15.2 diff --git a/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml index 034bf6477..a58043fca 100644 --- a/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml @@ -12,7 +12,7 @@ spec: terraform: state: s3: - bucketName: furyctl-e2e-deps-test-ci + bucketName: TERRAFORM_TF_STATES_BUCKET_NAME keyPrefix: furyctl/ region: eu-west-1 region: eu-west-1 diff --git a/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml b/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml index b72ccf291..97f4b8ab5 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml @@ -12,7 +12,7 @@ spec: terraform: state: s3: - bucketName: furyctl-e2e-deps-test-ci + bucketName: TERRAFORM_TF_STATES_BUCKET_NAME keyPrefix: furyctl/ region: eu-west-1 region: eu-west-1 diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index e9cc1d2fb..b657b0ef9 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -15,7 +15,7 @@ spec: terraform: state: s3: - bucketName: furyctl-e2e-deps-test-ci + bucketName: TERRAFORM_TF_STATES_BUCKET_NAME # changed from key, because each terraform project state will be placed in the directory defined by the prefix keyPrefix: furyctl/ region: eu-west-1 diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index c8bae2abb..dc8ef5493 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -188,9 +188,16 @@ var ( FuryctlValidateDependencies := func(basepath, binpath string) (string, error) { absBasepath := Abs(basepath) + cfgPath := filepath.Join(absBasepath, "furyctl.yaml") + + patchedCfgPath, err := patchFuryctlYaml(cfgPath) + if err != nil { + panic(err) + } + return RunCmd( furyctl, "validate", "dependencies", - "--config", filepath.Join(absBasepath, "furyctl.yaml"), + "--config", patchedCfgPath, "--distro-location", absBasepath, "--bin-path", binpath, "--debug", @@ -600,7 +607,8 @@ var ( ) // patch the furyctl.yaml's "spec.toolsConfiguration.terraform.state.s3.keyPrefix" key to add a timestamp and random int -// to avoid collisions in s3 when running tests in parallel, and also because the bucket is a super global resource. +// to avoid collisions in s3 when running tests in parallel, and also because the bucket is a super global resource, +// we also replace the "TERRAFORM_TF_STATES_BUCKET_NAME" with the actual bucket name from the env vars func patchFuryctlYaml(furyctlYamlPath string) (string, error) { furyctlYaml, err := os.ReadFile(furyctlYamlPath) if err != nil { @@ -610,8 +618,12 @@ func patchFuryctlYaml(furyctlYamlPath string) (string, error) { // we need to cap the string to 36 chars due to the s3 key prefix limit newKeyPrefix := fmt.Sprintf("furyctl-%d-%d", time.Now().UTC().Unix(), rand.Int())[0:36] + tfBucketName := os.Getenv("TERRAFORM_TF_STATES_BUCKET_NAME") + furyctlYaml = bytes.ReplaceAll(furyctlYaml, []byte("keyPrefix: furyctl/"), []byte("keyPrefix: "+newKeyPrefix+"/")) + furyctlYaml = bytes.ReplaceAll(furyctlYaml, []byte("TERRAFORM_TF_STATES_BUCKET_NAME"), []byte(tfBucketName)) + // create a temporary file to write the patched furyctl.yaml tmpFile, err := os.CreateTemp("", "furyctl.yaml") if err != nil { From e69ee26bfed6256381ea94baacb9c375b2c733ee Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Thu, 4 May 2023 11:52:50 +0200 Subject: [PATCH 280/383] chore: changed keyPrefix --- test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml | 2 +- test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml | 2 +- test/data/e2e/validate/dependencies/correct/furyctl.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml index a58043fca..409bf1b98 100644 --- a/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/infrastructure/data/furyctl.yaml @@ -13,7 +13,7 @@ spec: state: s3: bucketName: TERRAFORM_TF_STATES_BUCKET_NAME - keyPrefix: furyctl/ + keyPrefix: furyctl-next-create-cluster/ region: eu-west-1 region: eu-west-1 tags: diff --git a/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml b/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml index 97f4b8ab5..1bb269dc6 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml +++ b/test/data/e2e/create/cluster/kubernetes/data/furyctl.yaml @@ -13,7 +13,7 @@ spec: state: s3: bucketName: TERRAFORM_TF_STATES_BUCKET_NAME - keyPrefix: furyctl/ + keyPrefix: furyctl-next-create-cluster/ region: eu-west-1 region: eu-west-1 tags: diff --git a/test/data/e2e/validate/dependencies/correct/furyctl.yaml b/test/data/e2e/validate/dependencies/correct/furyctl.yaml index b657b0ef9..4614ca5f2 100644 --- a/test/data/e2e/validate/dependencies/correct/furyctl.yaml +++ b/test/data/e2e/validate/dependencies/correct/furyctl.yaml @@ -17,7 +17,7 @@ spec: s3: bucketName: TERRAFORM_TF_STATES_BUCKET_NAME # changed from key, because each terraform project state will be placed in the directory defined by the prefix - keyPrefix: furyctl/ + keyPrefix: furyctl-next-deps/ region: eu-west-1 # gcs: # bucketName: From ff972ea0fcca3a61af3b79d003430ce224d7756e Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Thu, 4 May 2023 16:47:10 +0200 Subject: [PATCH 281/383] Feat: prompt on critical resource delete (#374) * feat: added critical resources check after tf plan * feat: apply suggestions from code review Co-authored-by: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> Co-authored-by: Ramiro Algozino * feat: added create before destroy * fix: e2e tests * chore: added error return to Ask function --------- Co-authored-by: Giuseppe Iannelli <94362884+g-iannelli@users.noreply.github.com> Co-authored-by: Ramiro Algozino --- cmd/delete/cluster.go | 24 +- .../kfd/v1alpha2/eks/create/distribution.go | 2 +- .../kfd/v1alpha2/eks/create/infrastructure.go | 33 +- .../kfd/v1alpha2/eks/create/kubernetes.go | 32 +- .../kfd/v1alpha2/eks/delete/distribution.go | 2 +- .../kfd/v1alpha2/eks/delete/infrastructure.go | 2 +- .../kfd/v1alpha2/eks/delete/kubernetes.go | 2 +- internal/eks/infra.go | 19 + internal/eks/kubernetes.go | 19 + internal/parser/terraform.go | 72 ++++ internal/parser/terraform_test.go | 339 ++++++++++++++++++ internal/tool/terraform/runner.go | 11 +- internal/tool/terraform/runner_test.go | 2 +- internal/x/io/prompter.go | 36 ++ internal/x/io/prompter_test.go | 74 ++++ internal/x/slices/intersection.go | 23 ++ internal/x/slices/intersection_test.go | 74 ++++ 17 files changed, 740 insertions(+), 26 deletions(-) create mode 100644 internal/eks/infra.go create mode 100644 internal/eks/kubernetes.go create mode 100644 internal/parser/terraform.go create mode 100644 internal/parser/terraform_test.go create mode 100644 internal/x/io/prompter.go create mode 100644 internal/x/io/prompter_test.go create mode 100644 internal/x/slices/intersection.go create mode 100644 internal/x/slices/intersection_test.go diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 904178dba..249999d24 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -10,7 +10,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -22,6 +21,7 @@ import ( "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" netx "github.com/sighupio/furyctl/internal/x/net" ) @@ -179,7 +179,14 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while printing to stdout: %w", err) } - if !askForConfirmation() { + prompter := iox.NewPrompter(bufio.NewReader(os.Stdin)) + + prompt, err := prompter.Ask("yes") + if err != nil { + return fmt.Errorf("error reading user input: %w", err) + } + + if !prompt { return nil } } @@ -351,16 +358,3 @@ func getDeleteClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm HTTPS: https, }, nil } - -func askForConfirmation() bool { - reader := bufio.NewReader(os.Stdin) - - response, err := reader.ReadString('\n') - if err != nil { - return false - } - - response = strings.TrimSuffix(response, "\n") - - return strings.Compare(response, "yes") == 0 -} diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 9740cbf2c..0661015c3 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -171,7 +171,7 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error running terraform init: %w", err) } - if err := d.tfRunner.Plan(timestamp); err != nil && !d.dryRun { + if _, err := d.tfRunner.Plan(timestamp); err != nil && !d.dryRun { return fmt.Errorf("error running terraform plan: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 13ded6ed3..e5c21b076 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -5,6 +5,7 @@ package create import ( + "bufio" "bytes" "errors" "fmt" @@ -20,11 +21,14 @@ import ( "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/eks" + "github.com/sighupio/furyctl/internal/parser" "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/terraform" bytesx "github.com/sighupio/furyctl/internal/x/bytes" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" + "github.com/sighupio/furyctl/internal/x/slices" ) const SErrWrapWithStr = "%w: %s" @@ -33,6 +37,7 @@ var ( ErrVpcIDNotFound = errors.New("vpc_id not found in infra output") ErrVpcIDFromOut = errors.New("cannot read vpc_id from infrastructure's output.json") ErrWritingTfVars = errors.New("error writing terraform variables file") + ErrAbortedByUser = errors.New("aborted by user") ) type Infrastructure struct { @@ -103,7 +108,8 @@ func (i *Infrastructure) Exec() error { return fmt.Errorf("error running terraform init: %w", err) } - if err := i.tfRunner.Plan(timestamp); err != nil { + plan, err := i.tfRunner.Plan(timestamp) + if err != nil { return fmt.Errorf("error running terraform plan: %w", err) } @@ -111,6 +117,31 @@ func (i *Infrastructure) Exec() error { return nil } + tfParser := parser.NewTfPlanParser(string(plan)) + + parsedPlan := tfParser.Parse() + + eksInf := eks.NewInfra() + + criticalResources := slices.Intersection(eksInf.GetCriticalTFResourceTypes(), parsedPlan.Destroy) + + if len(criticalResources) > 0 { + logrus.Warnf("Deletion of the following critical resources has been detected: %s. See the logs for more details.", + strings.Join(criticalResources, ", ")) + logrus.Warn("Do you want to proceed? write 'yes' to continue or anything else to abort: ") + + prompter := iox.NewPrompter(bufio.NewReader(os.Stdin)) + + prompt, err := prompter.Ask("yes") + if err != nil { + return fmt.Errorf("error reading user input: %w", err) + } + + if !prompt { + return ErrAbortedByUser + } + } + logrus.Warn("Creating cloud resources, this could take a while...") if _, err := i.tfRunner.Apply(timestamp); err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 368c47b5c..d579f242d 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -5,6 +5,7 @@ package create import ( + "bufio" "bytes" "encoding/json" "errors" @@ -23,7 +24,9 @@ import ( "github.com/sighupio/fury-distribution/pkg/schema/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/eks" "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/parser" "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" @@ -32,6 +35,7 @@ import ( iox "github.com/sighupio/furyctl/internal/x/io" kubex "github.com/sighupio/furyctl/internal/x/kube" netx "github.com/sighupio/furyctl/internal/x/net" + "github.com/sighupio/furyctl/internal/x/slices" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) @@ -143,7 +147,8 @@ func (k *Kubernetes) Exec() error { return fmt.Errorf("error running terraform init: %w", err) } - if err := k.tfRunner.Plan(timestamp); err != nil { + plan, err := k.tfRunner.Plan(timestamp) + if err != nil { return fmt.Errorf("error running terraform plan: %w", err) } @@ -151,6 +156,31 @@ func (k *Kubernetes) Exec() error { return nil } + tfParser := parser.NewTfPlanParser(string(plan)) + + parsedPlan := tfParser.Parse() + + eksKube := eks.NewKubernetes() + + criticalResources := slices.Intersection(eksKube.GetCriticalTFResourceTypes(), parsedPlan.Destroy) + + if len(criticalResources) > 0 { + logrus.Warnf("Deletion of the following critical resources has been detected: %s. See the logs for more details.", + strings.Join(criticalResources, ", ")) + logrus.Warn("Do you want to proceed? write 'yes' to continue or anything else to abort: ") + + prompter := iox.NewPrompter(bufio.NewReader(os.Stdin)) + + prompt, err := prompter.Ask("yes") + if err != nil { + return fmt.Errorf("error reading user input: %w", err) + } + + if !prompt { + return ErrAbortedByUser + } + } + if k.furyctlConf.Spec.Kubernetes.ApiServer.PrivateAccess && !k.furyctlConf.Spec.Kubernetes.ApiServer.PublicAccess { logrus.Info("Checking connection to the VPC...") diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index cc34af819..501f0a0e5 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -134,7 +134,7 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error running terraform init: %w", err) } - if err := d.tfRunner.Plan(timestamp, "-destroy"); err != nil { + if _, err := d.tfRunner.Plan(timestamp, "-destroy"); err != nil { return fmt.Errorf("error running terraform plan: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index 2a37958c0..4d849b23a 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -74,7 +74,7 @@ func (i *Infrastructure) Exec() error { return nil } - if err := i.tfRunner.Plan(timestamp, "-destroy"); err != nil { + if _, err := i.tfRunner.Plan(timestamp, "-destroy"); err != nil { return fmt.Errorf("error running terraform plan: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 6e01ad1f6..8cb9a9e94 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -121,7 +121,7 @@ func (k *Kubernetes) Exec() error { return fmt.Errorf("error running terraform init: %w", err) } - if err := k.tfRunner.Plan(timestamp, "-destroy"); err != nil { + if _, err := k.tfRunner.Plan(timestamp, "-destroy"); err != nil { return fmt.Errorf("error running terraform plan: %w", err) } diff --git a/internal/eks/infra.go b/internal/eks/infra.go new file mode 100644 index 000000000..194da5db8 --- /dev/null +++ b/internal/eks/infra.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +type Infra struct { + criticalTFResourceTypes []string +} + +func NewInfra() *Infra { + return &Infra{ + criticalTFResourceTypes: []string{"aws_vpc", "aws_subnet"}, + } +} + +func (i *Infra) GetCriticalTFResourceTypes() []string { + return i.criticalTFResourceTypes +} diff --git a/internal/eks/kubernetes.go b/internal/eks/kubernetes.go new file mode 100644 index 000000000..ea7a592f7 --- /dev/null +++ b/internal/eks/kubernetes.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eks + +type Kubernetes struct { + criticalTFResourceTypes []string +} + +func NewKubernetes() *Kubernetes { + return &Kubernetes{ + criticalTFResourceTypes: []string{"aws_eks_cluster"}, + } +} + +func (k *Kubernetes) GetCriticalTFResourceTypes() []string { + return k.criticalTFResourceTypes +} diff --git a/internal/parser/terraform.go b/internal/parser/terraform.go new file mode 100644 index 000000000..6b0a2e2d5 --- /dev/null +++ b/internal/parser/terraform.go @@ -0,0 +1,72 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parser + +import "strings" + +const MinDiffLineTokensNum = 3 + +type TfPlanParser struct { + Plan string +} + +type TfPlan struct { + Destroy []string + Add []string + Change []string +} + +func NewTfPlanParser(plan string) *TfPlanParser { + return &TfPlanParser{ + Plan: plan, + } +} + +func (p *TfPlanParser) Parse() *TfPlan { + pl := TfPlan{ + Destroy: []string{}, + Add: []string{}, + Change: []string{}, + } + + planLines := strings.Split(p.Plan, "\n") + + for i, line := range planLines { + line = strings.TrimSpace(line) + + if !strings.HasPrefix(line, "#") || i+1 >= len(planLines) { + continue + } + + diffLineTokens := strings.Split(strings.TrimSpace(planLines[i+1]), " ") + + if len(diffLineTokens) < MinDiffLineTokensNum { + continue + } + + resourceName := strings.Trim(diffLineTokens[2], "\"") + + switch diffLineTokens[0] { + case "+": + pl.Add = append(pl.Add, resourceName) + + case "-": + pl.Destroy = append(pl.Destroy, resourceName) + + case "~": + pl.Change = append(pl.Change, resourceName) + + case "-/+": + pl.Destroy = append(pl.Destroy, resourceName) + pl.Add = append(pl.Add, resourceName) + + case "+/-": + pl.Add = append(pl.Add, resourceName) + pl.Destroy = append(pl.Destroy, resourceName) + } + } + + return &pl +} diff --git a/internal/parser/terraform_test.go b/internal/parser/terraform_test.go new file mode 100644 index 000000000..02f47d3d8 --- /dev/null +++ b/internal/parser/terraform_test.go @@ -0,0 +1,339 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parser_test + +import ( + "reflect" + "testing" + + "github.com/sighupio/furyctl/internal/parser" +) + +func TestNewTfPlanParser(t *testing.T) { + t.Parallel() + + type args struct { + plan string + } + + tests := []struct { + name string + args args + want *parser.TfPlanParser + }{ + { + name: "test empty plan", + args: args{ + plan: ``, + }, + want: &parser.TfPlanParser{ + Plan: ``, + }, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := parser.NewTfPlanParser(tt.args.plan); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewTfPlanParser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTfPlanParser_Parse(t *testing.T) { + t.Parallel() + + type fields struct { + plan string + } + + tests := []struct { + name string + fields fields + want *parser.TfPlan + }{ + { + name: "test empty plan", + fields: fields{ + plan: ``, + }, + want: &parser.TfPlan{ + Destroy: []string{}, + Add: []string{}, + Change: []string{}, + }, + }, + { + name: "test plan with no changes", + fields: fields{ + plan: `No changes. Infrastructure is up-to-date.`, + }, + want: &parser.TfPlan{ + Destroy: []string{}, + Add: []string{}, + Change: []string{}, + }, + }, + { + name: "test plan with add", + fields: fields{ + plan: `Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # module.vpc[0].module.vpc.aws_eip.nat[0] will be created + + resource "aws_eip" "nat" { + + allocation_id = (known after apply) + + association_id = (known after apply) + + carrier_ip = (known after apply) + + customer_owned_ip = (known after apply) + + domain = (known after apply) + + id = (known after apply) + + instance = (known after apply) + + network_border_group = (known after apply) + + network_interface = (known after apply) + + private_dns = (known after apply) + + private_ip = (known after apply) + + public_dns = (known after apply) + + public_ip = (known after apply) + + public_ipv4_pool = (known after apply) + + tags = { + + "Name" = "furyctl-dev-eu-west-2a" + + "kubernetes.io/cluster/furyctl-dev" = "shared" + } + + tags_all = { + + "Name" = "furyctl-dev-eu-west-2a" + + "env" = "demo" + + "kubernetes.io/cluster/furyctl-dev" = "shared" + } + + vpc = true + } + + # module.vpc[0].module.vpc.aws_vpc.this[0] will be created + + resource "aws_vpc" "this" { + + arn = (known after apply) + + assign_generated_ipv6_cidr_block = false + + cidr_block = "10.0.0.0/16" + + default_network_acl_id = (known after apply) + + default_route_table_id = (known after apply) + + default_security_group_id = (known after apply) + + dhcp_options_id = (known after apply) + + enable_classiclink = (known after apply) + + enable_classiclink_dns_support = (known after apply) + + enable_dns_hostnames = true + + enable_dns_support = true + + id = (known after apply) + + instance_tenancy = "default" + + ipv6_association_id = (known after apply) + + ipv6_cidr_block = (known after apply) + + main_route_table_id = (known after apply) + + owner_id = (known after apply) + + tags = { + + "Name" = "furyctl-dev" + + "kubernetes.io/cluster/furyctl-dev" = "shared" + } + + tags_all = { + + "Name" = "furyctl-dev" + + "env" = "demo" + + "kubernetes.io/cluster/furyctl-dev" = "shared" + } + } + +Plan: 2 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + vpc_cidr_block = "10.0.0.0/16" + + vpc_id = (known after apply)`, + }, + want: &parser.TfPlan{ + Destroy: []string{}, + Add: []string{"aws_eip", "aws_vpc"}, + Change: []string{}, + }, + }, + { + name: "test plan with destroy/create", + fields: fields{ + plan: `Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: +-/+ destroy and then create replacement + +Terraform will perform the following actions: + + # module.vpc[0].module.vpc.aws_route_table_association.private[2] must be replaced +-/+ resource "aws_route_table_association" "private" { + ~ id = "rtbassoc-076264f69a6f05f84" -> (known after apply) + ~ subnet_id = "subnet-0bf83e961b95609f1" -> (known after apply) # forces replacement + # (1 unchanged attribute hidden) + } + + # module.vpc[0].module.vpc.aws_subnet.private[2] must be replaced +-/+ resource "aws_subnet" "private" { + ~ arn = "arn:aws:ec2:eu-west-2:123456789123:subnet/subnet-0bf83e961b95609f1" -> (known after apply) + ~ availability_zone_id = "euw2-az1" -> (known after apply) + ~ cidr_block = "10.0.162.0/24" -> "10.0.152.0/24" # forces replacement + ~ id = "subnet-0bf83e961b95609f1" -> (known after apply) + + ipv6_cidr_block_association_id = (known after apply) + - map_customer_owned_ip_on_launch = false -> null + ~ owner_id = "492816857163" -> (known after apply) + tags = { + "Name" = "furyctl-dev-private-eu-west-2c" + "kubernetes.io/cluster/furyctl-dev" = "shared" + "kubernetes.io/role/internal-elb" = "1" + } + # (5 unchanged attributes hidden) + } + +Plan: 2 to add, 0 to change, 2 to destroy. + +Changes to Outputs: + ~ private_subnets = [ + # (1 unchanged element hidden) + "subnet-03056835b05d70b7f", + - "subnet-0bf83e961b95609f1", + + (known after apply), + ] + ~ private_subnets_cidr_blocks = [ + # (1 unchanged element hidden) + "10.0.172.0/24", + - "10.0.162.0/24", + + "10.0.152.0/24", + ]`, + }, + want: &parser.TfPlan{ + Destroy: []string{"aws_route_table_association", "aws_subnet"}, + Add: []string{"aws_route_table_association", "aws_subnet"}, + Change: []string{}, + }, + }, + { + name: "test plan with update in-place", + fields: fields{ + plan: `Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + ~ update in-place +-/+ destroy and then create replacement + +Terraform will perform the following actions: + + # module.vpn[0].aws_eip.vpn[0] will be updated in-place + ~ resource "aws_eip" "vpn" { + id = "eipalloc-02035b7d0eaec0b1c" + tags = {} + ~ tags_all = { + + "env" = "demo" + } + # (11 unchanged attributes hidden) + } + + # module.vpn[0].local_file.sshkeys must be replaced +-/+ resource "local_file" "sshkeys" { + ~ content = <<-EOT # forces replacement + users: + - name: Text-x + user_id: Text-x + + - name: Test + + user_id: Test + EOT + ~ id = "c739e95cf668b706029aac0d0b428bb3fa16a94b" -> (known after apply) + # (3 unchanged attributes hidden) + } + + # module.vpn[0].null_resource.ssh_users must be replaced +-/+ resource "null_resource" "ssh_users" { + ~ id = "1885518357807060104" -> (known after apply) + ~ triggers = { # forces replacement + ~ "sync-users" = "Text-x" -> "Text-x,Test" + # (1 unchanged element hidden) + } + } + +Plan: 2 to add, 1 to change, 2 to destroy.`, + }, + want: &parser.TfPlan{ + Destroy: []string{"local_file", "null_resource"}, + Add: []string{"local_file", "null_resource"}, + Change: []string{"aws_eip"}, + }, + }, + { + name: "test plan with destroy", + fields: fields{ + plan: `Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + - destroy + +Terraform will perform the following actions: + + # module.vpn[0].aws_eip.vpn[0] will be destroyed + - resource "aws_eip" "vpn" { + - association_id = "eipassoc-03acc973673630e2c" -> null + - domain = "vpc" -> null + - id = "eipalloc-02035b7d0eaec0b1c" -> null + - instance = "i-02e52a79b1fae934b" -> null + - network_border_group = "eu-west-2" -> null + - network_interface = "eni-0f0f91076c9b3ca5b" -> null + - private_dns = "ip-10-0-20-7.eu-west-2.compute.internal" -> null + - private_ip = "10.0.20.7" -> null + - public_dns = "ec2-13-42-227-161.eu-west-2.compute.amazonaws.com" -> null + - public_ip = "13.42.227.161" -> null + - public_ipv4_pool = "amazon" -> null + - tags = {} -> null + - tags_all = {} -> null + - vpc = true -> null + } + + # module.vpn[0].aws_eip_association.vpn[0] will be destroyed + - resource "aws_eip_association" "vpn" { + - allocation_id = "eipalloc-02035b7d0eaec0b1c" -> null + - id = "eipassoc-03acc973673630e2c" -> null + - instance_id = "i-02e52a79b1fae934b" -> null + - network_interface_id = "eni-0f0f91076c9b3ca5b" -> null + - private_ip_address = "10.0.20.7" -> null + - public_ip = "13.42.227.161" -> null + } + +Plan: 0 to add, 0 to change, 2 to destroy. + +Changes to Outputs: + - furyagent = (sensitive value) + - vpn_ip = [ + - "13.42.227.161", + ] -> null +`, + }, + want: &parser.TfPlan{ + Destroy: []string{"aws_eip", "aws_eip_association"}, + Add: []string{}, + Change: []string{}, + }, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + p := &parser.TfPlanParser{ + Plan: tt.fields.plan, + } + + got := p.Parse() + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parse() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index 80f08e892..b6e13bfdf 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -67,8 +67,9 @@ func (r *Runner) Init() error { return nil } -func (r *Runner) Plan(timestamp int64, params ...string) error { +func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { args := []string{"plan"} + out := []byte{} if len(params) > 0 { args = append(args, params...) @@ -82,7 +83,7 @@ func (r *Runner) Plan(timestamp int64, params ...string) error { WorkDir: r.paths.WorkDir, }) if err := cmd.Run(); err != nil { - return fmt.Errorf("command execution failed: %w", err) + return out, fmt.Errorf("command execution failed: %w", err) } err := os.WriteFile(path.Join(r.paths.Plan, @@ -90,10 +91,12 @@ func (r *Runner) Plan(timestamp int64, params ...string) error { cmd.Log.Out.Bytes(), iox.FullRWPermAccess) if err != nil { - return fmt.Errorf("error writing terraform plan log: %w", err) + return out, fmt.Errorf("error writing terraform plan log: %w", err) } - return nil + out = cmd.Log.Out.Bytes() + + return out, nil } func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { diff --git a/internal/tool/terraform/runner_test.go b/internal/tool/terraform/runner_test.go index c549e9011..eb37a7145 100644 --- a/internal/tool/terraform/runner_test.go +++ b/internal/tool/terraform/runner_test.go @@ -39,7 +39,7 @@ func Test_Runner_Plan(t *testing.T) { r := terraform.NewRunner(execx.NewFakeExecutor(), paths) - if err := r.Plan(42); err != nil { + if _, err := r.Plan(42); err != nil { t.Fatal(err) } diff --git a/internal/x/io/prompter.go b/internal/x/io/prompter.go new file mode 100644 index 000000000..9bbdb3d01 --- /dev/null +++ b/internal/x/io/prompter.go @@ -0,0 +1,36 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package iox + +import ( + "bufio" + "fmt" + "strings" +) + +type Prompter struct { + Reader *bufio.Reader +} + +func NewPrompter(r *bufio.Reader) *Prompter { + return &Prompter{ + Reader: r, + } +} + +func (p *Prompter) Ask(w string) (bool, error) { + response, err := p.Reader.ReadString('\n') + if err != nil { + return false, fmt.Errorf("error reading from stdin: %w", err) + } + + response = strings.TrimSuffix(response, "\n") + response = strings.Trim(response, " ") + + return strings.Compare( + strings.ToLower(response), + strings.ToLower(w), + ) == 0, nil +} diff --git a/internal/x/io/prompter_test.go b/internal/x/io/prompter_test.go new file mode 100644 index 000000000..577efb391 --- /dev/null +++ b/internal/x/io/prompter_test.go @@ -0,0 +1,74 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package iox_test + +import ( + "bufio" + "strings" + "testing" + + iox "github.com/sighupio/furyctl/internal/x/io" +) + +func TestPrompter_Ask(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + input string + promptWord string + expected bool + }{ + { + name: "user writes correct prompt input", + input: "yes\n", + promptWord: "yes", + expected: true, + }, + { + name: "user writes correct prompt input with multiple endline", + input: "yes\n\n\n", + promptWord: "yes", + expected: true, + }, + { + name: "user writes wrong prompt input", + input: "yessh\n", + promptWord: "yes", + expected: false, + }, + { + name: "user writes correct uppercase prompt input", + input: "YES\n\n\n", + promptWord: "yes", + expected: true, + }, + { + name: "user writes correct prompt input with whitespace", + input: " yes \n\n\n", + promptWord: "yes", + expected: true, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + prompter := iox.NewPrompter(bufio.NewReader(strings.NewReader(tc.input))) + + prompt, err := prompter.Ask(tc.promptWord) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if prompt != tc.expected { + t.Errorf("expected %v, got %v", tc.expected, !tc.expected) + } + }) + } +} diff --git a/internal/x/slices/intersection.go b/internal/x/slices/intersection.go new file mode 100644 index 000000000..e101afeff --- /dev/null +++ b/internal/x/slices/intersection.go @@ -0,0 +1,23 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +func Intersection[T comparable](a, b []T) []T { + unique := make(map[T]bool, len(a)) + + intersection := []T{} + + for _, v := range a { + unique[v] = true + } + + for _, w := range b { + if unique[w] { + intersection = append(intersection, w) + } + } + + return Uniq(intersection) +} diff --git a/internal/x/slices/intersection_test.go b/internal/x/slices/intersection_test.go new file mode 100644 index 000000000..878c9ca76 --- /dev/null +++ b/internal/x/slices/intersection_test.go @@ -0,0 +1,74 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices_test + +import ( + "reflect" + "testing" + + "github.com/sighupio/furyctl/internal/x/slices" +) + +func TestIntersection(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + a []string + b []string + want []string + }{ + { + name: "empty", + a: []string{}, + b: []string{}, + want: []string{}, + }, + { + name: "one element disjoint", + a: []string{"a"}, + b: []string{"b"}, + want: []string{}, + }, + { + name: "one element not disjoint", + a: []string{"a"}, + b: []string{"a"}, + want: []string{"a"}, + }, + { + name: "one element replicated not disjoint", + a: []string{"a"}, + b: []string{"a", "a"}, + want: []string{"a"}, + }, + { + name: "not disjoint", + a: []string{"a", "b", "c"}, + b: []string{"a", "d", "e"}, + want: []string{"a"}, + }, + { + name: "not disjoint with multiple element duplicated and different length", + a: []string{"a", "b", "c", "a"}, + b: []string{"a", "d", "e", "e", "a"}, + want: []string{"a"}, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := slices.Intersection(tc.a, tc.b) + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Intersection() = %v, want %v", got, tc.want) + } + }) + } +} From 4dae6bb90435d59a34714ba6f84b868e82604246 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 23 Mar 2023 10:41:13 +0100 Subject: [PATCH 282/383] feature: add `connect openvpn` command --- cmd/connect.go | 22 ++++++ cmd/connect/openvpn.go | 157 +++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 1 + 3 files changed, 180 insertions(+) create mode 100644 cmd/connect.go create mode 100644 cmd/connect/openvpn.go diff --git a/cmd/connect.go b/cmd/connect.go new file mode 100644 index 000000000..b972a3693 --- /dev/null +++ b/cmd/connect.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/sighupio/furyctl/cmd/connect" + "github.com/sighupio/furyctl/internal/analytics" +) + +func NewConnectCommand(tracker *analytics.Tracker) *cobra.Command { + connectCmd := &cobra.Command{ + Use: "connect", + } + + connectCmd.AddCommand(connect.NewOpenVPNCmd(tracker)) + + return connectCmd +} diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go new file mode 100644 index 000000000..e7c63074f --- /dev/null +++ b/cmd/connect/openvpn.go @@ -0,0 +1,157 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package connect + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/furyctl/internal/analytics" + "github.com/sighupio/furyctl/internal/cmd/cmdutil" + cobrax "github.com/sighupio/furyctl/internal/x/cobra" + execx "github.com/sighupio/furyctl/internal/x/exec" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +var ( + ErrParsingFlag = errors.New("error while parsing flag") +) + +type OpenVPNCmdFlags struct { + Profile string + FuryctlPath string +} + +func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { + var cmdEvent analytics.Event + + cmd := &cobra.Command{ + Use: "openvpn", + Short: "Connect to OpenVPN with the specified profile name", + PreRun: func(cmd *cobra.Command, _ []string) { + cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + // check if user is root + // userIsRoot, err := osx.IsRoot() + // if err != nil { + // return fmt.Errorf("error while checking if user is root: %w", err) + // } + // if !userIsRoot { + // return errors.New("please run this command as root (with 'sudo')") + // } + + // parse flags + flags, err := getOpenVPNCmdFlags(cmd, tracker, cmdEvent) + if err != nil { + return err + } + if flags.Profile == "" { + return errors.New("profile flag is required") + } + + // get home dir + homeDir, err := os.UserHomeDir() + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return fmt.Errorf("error while getting current working directory: %w", err) + } + + // parse furyctl.yaml config + furyctlConf, err := yamlx.FromFileV3[config.Furyctl](flags.FuryctlPath) + if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return err + } + + // set common paths + basePath := filepath.Join(homeDir, ".furyctl", furyctlConf.Metadata.Name) + openVPNWorkDir := filepath.Join(basePath, "infrastructure", "terraform", "secrets") + // openVPNPidFile := filepath.Join(basePath, "openvpn.pid") + + // check if openvpn.pid file exist + // _, err = os.Stat(openVPNPidFile) + // if err == nil { + // return errors.New("openvpn seems to be already connected, please use 'disconnect' command to disconnect and terminate the openvpn process") + // } + // if err != nil { + // if !errors.Is(err, os.ErrNotExist) { + // cmdEvent.AddErrorMessage(err) + // tracker.Track(cmdEvent) + // return err + // } + // } + + executor := execx.NewStdExecutor() + openVPNCmd := execx.NewCmd("sudo", execx.CmdOptions{ + Args: []string{"openvpn", "--config", fmt.Sprintf("%s-%s.ovpn", furyctlConf.Metadata.Name, flags.Profile)}, + Executor: executor, + WorkDir: openVPNWorkDir, + }) + + // start openvpn process + if err := openVPNCmd.Run(); err != nil { + err = fmt.Errorf("error while running openvpn: %w", err) + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return err + } + + // save process pid to openvpn.pid + // if err := iox.WriteFile(openVPNPidFile, []byte(strconv.Itoa(openVPNCmd.Process.Pid))); err != nil { + // return fmt.Errorf("error while writing pid file: %w", err) + // } + + // cmdEvent.AddSuccessMessage("connect openvpn succeeded") + // tracker.Track(cmdEvent) + + return nil + }, + } + + setupOpenVPNCmdFlags(cmd) + + return cmd +} + +func getOpenVPNCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cmdEvent analytics.Event) (OpenVPNCmdFlags, error) { + furyctlPath, err := cmdutil.StringFlag(cmd, "config", tracker, cmdEvent) + if err != nil { + return OpenVPNCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "config") + } + + profile, err := cmdutil.StringFlag(cmd, "profile", tracker, cmdEvent) + if err != nil { + return OpenVPNCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "profile") + } + + return OpenVPNCmdFlags{ + Profile: profile, + FuryctlPath: furyctlPath, + }, nil +} + +func setupOpenVPNCmdFlags(cmd *cobra.Command) { + cmd.Flags().StringP( + "config", + "c", + "furyctl.yaml", + "Path to the configuration file", + ) + + cmd.Flags().StringP( + "profile", + "p", + "", + "Name of to the OpenVPN profile", + ) +} diff --git a/cmd/root.go b/cmd/root.go index 4bffca666..8e81915cd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -195,6 +195,7 @@ furyctl is a command line interface tool to manage the full lifecycle of a Kuber rootCmd.AddCommand(NewVersionCmd(versions, tracker)) rootCmd.AddCommand(NewDeleteCommand(tracker)) rootCmd.AddCommand(NewLegacyCommand(tracker)) + rootCmd.AddCommand(NewConnectCommand(tracker)) return rootCmd } From 04718095ac0ce84df771fcb0dd1057f8b2c05d62 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 2 May 2023 17:23:54 +0200 Subject: [PATCH 283/383] fix: address linting issues --- cmd/connect/openvpn.go | 50 ++++++++++-------------------------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go index e7c63074f..3fa4e68bc 100644 --- a/cmd/connect/openvpn.go +++ b/cmd/connect/openvpn.go @@ -21,7 +21,8 @@ import ( ) var ( - ErrParsingFlag = errors.New("error while parsing flag") + ErrParsingFlag = errors.New("error while parsing flag") + ErrProfileFlagRequired = errors.New("profile flag is required") ) type OpenVPNCmdFlags struct { @@ -39,57 +40,37 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, RunE: func(cmd *cobra.Command, _ []string) error { - // check if user is root - // userIsRoot, err := osx.IsRoot() - // if err != nil { - // return fmt.Errorf("error while checking if user is root: %w", err) - // } - // if !userIsRoot { - // return errors.New("please run this command as root (with 'sudo')") - // } - - // parse flags + // Parse flags. flags, err := getOpenVPNCmdFlags(cmd, tracker, cmdEvent) if err != nil { return err } + if flags.Profile == "" { - return errors.New("profile flag is required") + return ErrProfileFlagRequired } - // get home dir + // Get home dir. homeDir, err := os.UserHomeDir() if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) + return fmt.Errorf("error while getting current working directory: %w", err) } - // parse furyctl.yaml config + // Parse furyctl.yaml config. furyctlConf, err := yamlx.FromFileV3[config.Furyctl](flags.FuryctlPath) if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) + return err } - // set common paths + // Set common paths. basePath := filepath.Join(homeDir, ".furyctl", furyctlConf.Metadata.Name) openVPNWorkDir := filepath.Join(basePath, "infrastructure", "terraform", "secrets") - // openVPNPidFile := filepath.Join(basePath, "openvpn.pid") - - // check if openvpn.pid file exist - // _, err = os.Stat(openVPNPidFile) - // if err == nil { - // return errors.New("openvpn seems to be already connected, please use 'disconnect' command to disconnect and terminate the openvpn process") - // } - // if err != nil { - // if !errors.Is(err, os.ErrNotExist) { - // cmdEvent.AddErrorMessage(err) - // tracker.Track(cmdEvent) - // return err - // } - // } executor := execx.NewStdExecutor() openVPNCmd := execx.NewCmd("sudo", execx.CmdOptions{ @@ -98,22 +79,15 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { WorkDir: openVPNWorkDir, }) - // start openvpn process + // Start openvpn process. if err := openVPNCmd.Run(); err != nil { err = fmt.Errorf("error while running openvpn: %w", err) cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) + return err } - // save process pid to openvpn.pid - // if err := iox.WriteFile(openVPNPidFile, []byte(strconv.Itoa(openVPNCmd.Process.Pid))); err != nil { - // return fmt.Errorf("error while writing pid file: %w", err) - // } - - // cmdEvent.AddSuccessMessage("connect openvpn succeeded") - // tracker.Track(cmdEvent) - return nil }, } From c73ec758fe40185dc820ecb2be45c29bcf80c904 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 8 May 2023 14:00:48 +0200 Subject: [PATCH 284/383] add error variables --- cmd/connect/openvpn.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go index 3fa4e68bc..274a41273 100644 --- a/cmd/connect/openvpn.go +++ b/cmd/connect/openvpn.go @@ -21,8 +21,10 @@ import ( ) var ( - ErrParsingFlag = errors.New("error while parsing flag") + ErrParsingFlag = errors.New("cannot parse command-line flag") ErrProfileFlagRequired = errors.New("profile flag is required") + ErrRunningOpenVPN = errors.New("cannot run openvpn") + ErrCannotGetHomeDir = errors.New("cannot get current working directory") ) type OpenVPNCmdFlags struct { @@ -56,7 +58,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("error while getting current working directory: %w", err) + return fmt.Errorf("%s: %w", ErrCannotGetHomeDir, err) } // Parse furyctl.yaml config. @@ -81,7 +83,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { // Start openvpn process. if err := openVPNCmd.Run(); err != nil { - err = fmt.Errorf("error while running openvpn: %w", err) + err = fmt.Errorf("%w: %w", ErrRunningOpenVPN, err) cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) From 038c06b5ac714743712065195cb075baa1ae916c Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 8 May 2023 17:02:22 +0200 Subject: [PATCH 285/383] wrap errors --- cmd/connect/openvpn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go index 274a41273..b7c717ce3 100644 --- a/cmd/connect/openvpn.go +++ b/cmd/connect/openvpn.go @@ -58,7 +58,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("%s: %w", ErrCannotGetHomeDir, err) + return fmt.Errorf("%w: %s", ErrCannotGetHomeDir, err) } // Parse furyctl.yaml config. @@ -83,7 +83,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { // Start openvpn process. if err := openVPNCmd.Run(); err != nil { - err = fmt.Errorf("%w: %w", ErrRunningOpenVPN, err) + err = fmt.Errorf("%w: %s", ErrRunningOpenVPN, err) cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) From 2dd82b63465b28fa3525ae03260cab186ab33597 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 8 May 2023 17:05:04 +0200 Subject: [PATCH 286/383] double wraps --- cmd/connect/openvpn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go index b7c717ce3..33447d66b 100644 --- a/cmd/connect/openvpn.go +++ b/cmd/connect/openvpn.go @@ -58,7 +58,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) - return fmt.Errorf("%w: %s", ErrCannotGetHomeDir, err) + return fmt.Errorf("%w: %w", ErrCannotGetHomeDir, err) } // Parse furyctl.yaml config. @@ -83,7 +83,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { // Start openvpn process. if err := openVPNCmd.Run(); err != nil { - err = fmt.Errorf("%w: %s", ErrRunningOpenVPN, err) + err = fmt.Errorf("%w: %w", ErrRunningOpenVPN, err) cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) From 719792060891a75f010644ce68d3ba1d112b0bef Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 20 Apr 2023 16:15:58 +0200 Subject: [PATCH 287/383] feat: add timeout flag --- cmd/create/cluster.go | 15 +- .../kfd/v1alpha2/eks/create/distribution.go | 42 ++++ .../kfd/v1alpha2/eks/create/infrastructure.go | 76 ++++++++ .../kfd/v1alpha2/eks/create/kubernetes.go | 37 ++++ internal/apis/kfd/v1alpha2/eks/creator.go | 182 ++++++++++++++++-- internal/cluster/creator.go | 2 +- internal/cmd/cmdutil/flag.go | 12 ++ internal/tool/ansible/runner.go | 23 ++- internal/tool/awscli/runner.go | 43 +++-- internal/tool/furyagent/runner.go | 36 ++-- internal/tool/kubectl/runner.go | 70 ++++--- internal/tool/kustomize/runner.go | 33 ++-- internal/tool/openvpn/runner.go | 33 ++-- internal/tool/runner.go | 1 + internal/tool/runner_test.go | 2 - internal/tool/terraform/runner.go | 94 ++++++--- internal/x/exec/cmd.go | 16 ++ internal/x/exec/cmd_test.go | 37 +++- 18 files changed, 617 insertions(+), 137 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index b6cf2e78e..185c9e0ff 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -47,6 +47,7 @@ type ClusterCmdFlags struct { NoTTY bool HTTPS bool Kubeconfig string + Timeout int } func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { @@ -219,7 +220,7 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while initializing cluster creation: %w", err) } - if err := clusterCreator.Create(flags.SkipPhase); err != nil { + if err := clusterCreator.Create(flags.SkipPhase, flags.Timeout); err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) @@ -332,6 +333,11 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "https") } + timeout, err := cmdutil.IntFlag(cmd, "timeout", tracker, cmdEvent) + if err != nil { + return ClusterCmdFlags{}, fmt.Errorf("%w: %s", ErrParsingFlag, "timeout") + } + return ClusterCmdFlags{ Debug: debug, FuryctlPath: furyctlPath, @@ -347,6 +353,7 @@ func getCreateClusterCmdFlags(cmd *cobra.Command, tracker *analytics.Tracker, cm NoTTY: noTTY, Kubeconfig: kubeconfig, HTTPS: https, + Timeout: timeout, }, nil } @@ -427,4 +434,10 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "", "Path to the kubeconfig file, mandatory if you want to run the distribution phase alone and the KUBECONFIG environment variable is not set", ) + + cmd.Flags().Int( + "timeout", + 60, + "Timeout in seconds for the whole cluster creation process. Expressed in seconds", + ) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 0661015c3..b6ebb2cdf 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -11,6 +11,7 @@ import ( "os" "path" "path/filepath" + "sync" "time" "github.com/sirupsen/logrus" @@ -235,6 +236,47 @@ func (d *Distribution) Exec() error { return d.applyManifests(manifestsOutPath) } +func (d *Distribution) Stop() []error { + // use gorooutines to stop all the tools + var wg sync.WaitGroup + errChan := make(chan error, 1) + + wg.Add(3) + + go func() { + defer wg.Done() + if err := d.tfRunner.Stop(); err != nil { + errChan <- err + } + }() + + go func() { + defer wg.Done() + if err := d.kzRunner.Stop(); err != nil { + errChan <- err + } + }() + + go func() { + defer wg.Done() + if err := d.kubeRunner.Stop(); err != nil { + errChan <- err + } + }() + + wg.Wait() + + close(errChan) + + errs := make([]error, 0) + + for err := range errChan { + errs = append(errs, err) + } + + return errs +} + func (d *Distribution) createFuryctlMerger() (*merge.Merger, error) { defaultsFilePath := path.Join(d.distroPath, "furyctl-defaults.yaml") diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index e5c21b076..50998f8d4 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -11,8 +11,10 @@ import ( "fmt" "io/fs" "os" + "os/exec" "path" "strings" + "sync" "time" "github.com/sirupsen/logrus" @@ -151,6 +153,80 @@ func (i *Infrastructure) Exec() error { return nil } +func (i *Infrastructure) Stop() []error { + var wg sync.WaitGroup + errChan := make(chan error, 1) + + wg.Add(3) + + if i.ovRunner != nil { + fmt.Println("Stopping openvpn...") + go func() { + defer wg.Done() + if err := i.ovRunner.Stop(); err != nil { + errChan <- fmt.Errorf("error stopping openvpn: %v", err) + } + }() + } + + if i.faRunner != nil { + fmt.Println("Stopping furyagent...") + go func() { + defer wg.Done() + if err := i.faRunner.Stop(); err != nil { + errChan <- fmt.Errorf("error stopping furyagent: %v", err) + } + }() + } + + if i.tfRunner != nil { + fmt.Println("Stopping terraform...") + go func() { + defer wg.Done() + if err := i.tfRunner.Stop(); err != nil { + errChan <- fmt.Errorf("error stopping terraform: %v", err) + } + }() + } + + wg.Wait() + + close(errChan) + + errs := make([]error, 0) + + for err := range errChan { + errs = append(errs, err) + } + + return errs +} + +func (i *Infrastructure) isVpnConfigured() bool { + vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn + if vpn == nil { + return false + } + + instances := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances + if instances == nil { + return true + } + + return *instances > 0 +} + +func (i *Infrastructure) generateClientName() (string, error) { + whoamiResp, err := exec.Command("whoami").Output() + if err != nil { + return "", fmt.Errorf("error getting current user: %w", err) + } + + whoami := strings.TrimSpace(string(whoamiResp)) + + return fmt.Sprintf("%s-%s", i.furyctlConf.Metadata.Name, whoami), nil +} + func (i *Infrastructure) copyFromTemplate() error { var cfg template.Config diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index d579f242d..9fcf07e90 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -16,6 +16,7 @@ import ( "path" "strconv" "strings" + "sync" "time" "github.com/sirupsen/logrus" @@ -261,6 +262,42 @@ func (*Kubernetes) getCommonDataFromDistribution(furyctlCfg template.Config) (ma return nodeSelector, tolerations, nil } +func (k *Kubernetes) Stop() []error { + // use goroutines to stop runners + var wg sync.WaitGroup + errChan := make(chan error, 1) + + wg.Add(2) + + go func() { + defer wg.Done() + + if err := k.tfRunner.Stop(); err != nil { + errChan <- err + } + }() + + go func() { + defer wg.Done() + + if err := k.awsRunner.Stop(); err != nil { + errChan <- err + } + }() + + wg.Wait() + + close(errChan) + + errs := make([]error, 0) + + for err := range errChan { + errs = append(errs, err) + } + + return errs +} + func (k *Kubernetes) copyFromTemplate(furyctlCfg template.Config) error { var cfg template.Config diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 2cafe4e53..1ebf67533 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -11,6 +11,8 @@ import ( "path" "path/filepath" "strings" + "sync" + "time" "github.com/sirupsen/logrus" @@ -27,6 +29,8 @@ import ( var ( ErrUnsupportedPhase = errors.New("unsupported phase") ErrInfraNotPresent = errors.New("the configuration file does not contain an infrastructure section") + ErrClusterCreation = errors.New("cluster creation failed") + ErrTimeout = errors.New("timeout reached") ) type ClusterCreator struct { @@ -106,7 +110,7 @@ func (v *ClusterCreator) SetProperty(name string, value any) { } } -func (v *ClusterCreator) Create(skipPhase string) error { +func (v *ClusterCreator) Create(skipPhase string, timeout int) error { infra, kube, distro, err := v.setupPhases() if err != nil { return err @@ -131,27 +135,171 @@ func (v *ClusterCreator) Create(skipPhase string) error { return fmt.Errorf("error while creating vpn connector: %w", err) } - switch v.phase { - case cluster.OperationPhaseInfrastructure: - if err := v.infraPhase(infra, vpnConnector); err != nil { - return err - } + errCh := make(chan error) + execCloseCh := make(chan bool) + var wg sync.WaitGroup - case cluster.OperationPhaseKubernetes: - if err := v.kubernetesPhase(kube, vpnConnector); err != nil { - return err - } + wg.Add(1) - case cluster.OperationPhaseDistribution: - if err := v.distributionPhase(distro, vpnConnector); err != nil { - return err + go func(closeCh chan bool, errChan chan error) { + defer func() { + closeCh <- true + }() + + switch v.phase { + case cluster.OperationPhaseInfrastructure: + if v.furyctlConf.Spec.Infrastructure == nil { + absPath, err := filepath.Abs(v.paths.ConfigPath) + if err != nil { + logrus.Debugf("error while getting absolute path of %s: %v", v.paths.ConfigPath, err) + + errCh <- fmt.Errorf("%w: at %s", ErrInfraNotPresent, v.paths.ConfigPath) + + return + } + + errCh <- fmt.Errorf("%w: check at %s", ErrInfraNotPresent, absPath) + + return + } + + if err = infra.Exec(infraOpts); err != nil { + errCh <- fmt.Errorf("error while executing infrastructure phase: %w", err) + + return + } + + if v.dryRun { + logrus.Info("Infrastructure created successfully (dry-run mode)") + } + + logrus.Info("Infrastructure created successfully") + + if v.furyctlConf.Spec.Infrastructure != nil { + if v.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil && v.vpnAutoConnect { + logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") + } + } + + break + + case cluster.OperationPhaseKubernetes: + logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + + " (e.g. check VPN connection is active`), otherwise the installation will fail.") + + if err = kube.Exec(); err != nil { + errCh <- fmt.Errorf("error while executing kubernetes phase: %w", err) + + return + } + + if v.dryRun { + logrus.Info("Kubernetes cluster created successfully (dry-run mode)") + + return + } + + if err := v.storeClusterConfig(); err != nil { + errCh <- fmt.Errorf("error while creating secret with the cluster configuration: %w", err) + + return + } + + logrus.Info("Kubernetes cluster created successfully") + + err = v.logKubeconfig() + if err != nil { + errCh <- fmt.Errorf("error while logging kubeconfig path: %w", err) + + return + } + + break + + case cluster.OperationPhaseDistribution: + if err = distro.Exec(); err != nil { + errCh <- fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) + + return + } + + if v.dryRun { + logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") + + return + } + + if err := v.storeClusterConfig(); err != nil { + errCh <- fmt.Errorf("error while creating secret with the cluster configuration: %w", err) + + return + } + + logrus.Info("Kubernetes Fury Distribution installed successfully") + + break + + case cluster.OperationPhaseAll: + errCh <- v.allPhases(skipPhase, infraOpts, infra, kube, distro) + + return + + default: + errCh <- ErrUnsupportedPhase } - case cluster.OperationPhaseAll: - return v.allPhases(skipPhase, infra, kube, distro, vpnConnector) + }(execCloseCh, errCh) + + strBuilder := strings.Builder{} + + for { + select { + case <-time.After(time.Duration(timeout) * time.Second): + fmt.Println("I'm select: timeout reached") - default: - return ErrUnsupportedPhase + fmt.Println("I'm select: stopping execChan") + wg.Done() + fmt.Println("I'm select: stopping errChan") + close(errCh) + + errs := make([]error, 0) + errs = append(errs, ErrTimeout) + + fmt.Println("I'm select: Waiting for all processes to stop...") + + if infra != nil { + errs = append(errs, infra.Stop()...) + } + + if kube != nil { + errs = append(errs, kube.Stop()...) + } + + if distro != nil { + errs = append(errs, distro.Stop()...) + } + + fmt.Println("I'm select: returning error") + + for _, err := range errs { + strBuilder.WriteString(err.Error()) + strBuilder.WriteString(" - ") + } + + return fmt.Errorf(strBuilder.String()) + + case r := <-execCloseCh: + if r { + close(execCloseCh) + close(errCh) + + return fmt.Errorf(strBuilder.String()) + } + + case err := <-errCh: + strBuilder.WriteString(err.Error()) + strBuilder.WriteString(" - ") + } } return nil diff --git a/internal/cluster/creator.go b/internal/cluster/creator.go index 2ee475a1c..d1685facd 100644 --- a/internal/cluster/creator.go +++ b/internal/cluster/creator.go @@ -51,7 +51,7 @@ type CreatorProperty struct { type Creator interface { SetProperties(props []CreatorProperty) SetProperty(name string, value any) - Create(skipPhase string) error + Create(skipPhase string, timeout int) error } func NewCreator( diff --git a/internal/cmd/cmdutil/flag.go b/internal/cmd/cmdutil/flag.go index 6591f8a28..a4fe9aac2 100644 --- a/internal/cmd/cmdutil/flag.go +++ b/internal/cmd/cmdutil/flag.go @@ -27,6 +27,18 @@ func BoolFlag(cmd *cobra.Command, flagName string, tracker *analytics.Tracker, e return value, nil } +func IntFlag(cmd *cobra.Command, flagName string, tracker *analytics.Tracker, event analytics.Event) (int, error) { + value, err := cmd.Flags().GetInt(flagName) + if err != nil { + event.AddErrorMessage(fmt.Errorf("%w: %s", ErrParsingFlag, flagName)) + tracker.Track(event) + + return 0, fmt.Errorf("%w: %s", ErrParsingFlag, flagName) + } + + return value, nil +} + func StringFlag(cmd *cobra.Command, flagName string, tracker *analytics.Tracker, event analytics.Event) (string, error) { value, err := cmd.Flags().GetString(flagName) if err != nil { diff --git a/internal/tool/ansible/runner.go b/internal/tool/ansible/runner.go index 329b03c32..519c51a23 100644 --- a/internal/tool/ansible/runner.go +++ b/internal/tool/ansible/runner.go @@ -18,12 +18,17 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmd: execx.NewCmd(paths.Ansible, execx.CmdOptions{ + Executor: executor, + WorkDir: paths.WorkDir, + }), } } @@ -32,14 +37,22 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Version() (string, error) { - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Ansible, execx.CmdOptions{ - Args: []string{"--version"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + args := []string{r.paths.Ansible, "--version"} + + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error getting ansible version: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + if err := r.cmd.Stop(); err != nil { + return fmt.Errorf("error stopping ansible runner: %w", err) + } + + return nil +} diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index a25c75451..d09437abf 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -18,12 +18,17 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmd: execx.NewCmd(paths.Awscli, execx.CmdOptions{ + Executor: executor, + WorkDir: paths.WorkDir, + }), } } @@ -32,17 +37,15 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Ec2(sub string, params ...string) (string, error) { - args := []string{"ec2", sub} + args := []string{r.paths.Awscli, "ec2", sub} if len(params) > 0 { args = append(args, params...) } - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error running awscli ec2 %s: %w", sub, err) } @@ -67,14 +70,12 @@ func (r *Runner) S3(params ...string) (string, error) { } func (r *Runner) S3Api(params ...string) (string, error) { - args := []string{"s3api"} + args := []string{r.paths.Awscli, "s3api"} args = append(args, params...) - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error executing awscli s3api: %w", err) } @@ -102,14 +103,22 @@ func (r *Runner) Route53(sub string, params ...string) (string, error) { } func (r *Runner) Version() (string, error) { - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ - Args: []string{"--version"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + args := []string{r.paths.Awscli, "--version"} + + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error getting awscli version: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + if err := r.cmd.Stop(); err != nil { + return fmt.Errorf("error stopping awscli runner: %w", err) + } + + return nil +} diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index 4a2c45571..3f1fdc182 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -19,12 +19,17 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmd: execx.NewCmd(paths.Furyagent, execx.CmdOptions{ + Executor: executor, + WorkDir: paths.WorkDir, + }), } } @@ -34,36 +39,39 @@ func (r *Runner) CmdPath() string { func (r *Runner) ConfigOpenvpnClient(name string, params ...string) (*bytes.Buffer, error) { args := []string{ + r.paths.Furyagent, "configure", "openvpn-client", fmt.Sprintf("--client-name=%s", name), "--config=furyagent.yml", } - args = append(args, params...) + r.cmd.Args = args - cmd := execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }) - - if err := cmd.Run(); err != nil { + if err := r.cmd.Run(); err != nil { return nil, fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) } - return cmd.Log.Out, nil + return r.cmd.Log.Out, nil } func (r *Runner) Version() (string, error) { - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ - Args: []string{"version"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + args := []string{r.paths.Furyagent, "version"} + + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error getting furyagent version: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + if err := r.cmd.Stop(); err != nil { + return fmt.Errorf("error stopping furyagent runner: %w", err) + } + + return nil +} diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index 164fd6a11..a95816fa6 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -24,6 +24,7 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd serverSide bool skipNotFound bool clientVersion bool @@ -36,6 +37,10 @@ func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound, c serverSide: serverSide, skipNotFound: skipNotFound, clientVersion: clientVersion, + cmd: execx.NewCmd(paths.Kubectl, execx.CmdOptions{ + Executor: executor, + WorkDir: paths.WorkDir, + }), } } @@ -44,7 +49,7 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Apply(manifestPath string, params ...string) error { - args := []string{"apply"} + args := []string{r.paths.Kubectl, "apply"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -60,12 +65,9 @@ func (r *Runner) Apply(manifestPath string, params ...string) error { args = append(args, "-f", manifestPath) - _, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) - if err != nil { + r.cmd.Args = args + + if _, err := execx.CombinedOutput(r.cmd); err != nil { return fmt.Errorf("error applying manifests: %w", err) } @@ -73,7 +75,7 @@ func (r *Runner) Apply(manifestPath string, params ...string) error { } func (r *Runner) Get(ns string, params ...string) (string, error) { - args := []string{"get"} + args := []string{r.paths.Kubectl, "get"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -87,11 +89,9 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { args = append(args, params...) - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return out, fmt.Errorf("error while getting resources: %w", err) } @@ -100,7 +100,10 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { } func (r *Runner) APIResources(params ...string) (string, error) { - args := []string{"api-resources"} + args := []string{ + r.paths.Kubectl, + "api-resources", + } if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -127,11 +130,9 @@ func (r *Runner) GetResource(ns, res, name string) (string, error) { args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return out, fmt.Errorf("error while getting resources: %w", err) } @@ -200,7 +201,10 @@ func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { } func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { - args := []string{"delete"} + args := []string{ + r.paths.Kubectl, + "delete", + } if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -216,11 +220,10 @@ func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { args = append(args, "-f", manifestPath) - res, err := execx.CombinedOutputWithTimeout(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }), kubectlDeleteTimeout) + r.cmd.Args = args + + _, err := execx.CombinedOutputWithTimeout(r.cmd, kubectlDeleteTimeout) + if err != nil { return res, fmt.Errorf("error deleting resources: %w", err) } @@ -229,7 +232,7 @@ func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { } func (r *Runner) Version() (string, error) { - args := []string{"version"} + args := []string{r.paths.Kubectl, "version"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -241,13 +244,20 @@ func (r *Runner) Version() (string, error) { args = append(args, "-o", "json") - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - })) + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error getting kubectl version: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + if err := r.cmd.Stop(); err != nil { + return fmt.Errorf("error stopping kubectl runner: %w", err) + } + + return nil +} diff --git a/internal/tool/kustomize/runner.go b/internal/tool/kustomize/runner.go index 6a403e39b..6e3162d9b 100644 --- a/internal/tool/kustomize/runner.go +++ b/internal/tool/kustomize/runner.go @@ -18,12 +18,17 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmd: execx.NewCmd(paths.Kustomize, execx.CmdOptions{ + Executor: executor, + WorkDir: paths.WorkDir, + }), } } @@ -32,11 +37,11 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Version() (string, error) { - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kustomize, execx.CmdOptions{ - Args: []string{"version", "--short"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + args := []string{r.paths.Kustomize, "version", "--short"} + + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error getting kustomize version: %w", err) } @@ -45,16 +50,22 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Build() (string, error) { - args := []string{"build", "--load_restrictor", "none", "."} + args := []string{r.paths.Kustomize, "build", "--load_restrictor", "none", "."} - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kustomize, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error while running kustomize build: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + if err := r.cmd.Stop(); err != nil { + return fmt.Errorf("error stopping kustomize runner: %w", err) + } + + return nil +} diff --git a/internal/tool/openvpn/runner.go b/internal/tool/openvpn/runner.go index bf3741bf3..04a334084 100644 --- a/internal/tool/openvpn/runner.go +++ b/internal/tool/openvpn/runner.go @@ -19,12 +19,17 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmd: execx.NewCmd(paths.Openvpn, execx.CmdOptions{ + Executor: executor, + WorkDir: paths.WorkDir, + }), } } @@ -46,12 +51,10 @@ func (r *Runner) Connect(name string) error { args = args[1:] } - err = execx.NewCmd(path, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }).Run() - if err != nil { + r.cmd.Args = args + r.cmd.Path = path + + if err := r.cmd.Run(); err != nil { return fmt.Errorf("error while running openvpn: %w", err) } @@ -59,14 +62,22 @@ func (r *Runner) Connect(name string) error { } func (r *Runner) Version() (string, error) { - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Openvpn, execx.CmdOptions{ - Args: []string{"--version"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + args := []string{r.paths.Openvpn, "--version"} + + r.cmd.Args = args + + out, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error getting openvpn version: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + if err := r.cmd.Stop(); err != nil { + return fmt.Errorf("error stopping openvpn runner: %w", err) + } + + return nil +} diff --git a/internal/tool/runner.go b/internal/tool/runner.go index 0370719ee..a8f894efd 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -32,6 +32,7 @@ const ( type Runner interface { Version() (string, error) CmdPath() string + Stop() error } type RunnerFactoryPaths struct { diff --git a/internal/tool/runner_test.go b/internal/tool/runner_test.go index 2612f9833..ebf9ad322 100644 --- a/internal/tool/runner_test.go +++ b/internal/tool/runner_test.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build unit - package tool_test import ( diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index b6e13bfdf..b0f02e8c5 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -18,7 +18,10 @@ import ( iox "github.com/sighupio/furyctl/internal/x/io" ) -var errOutputFromApply = errors.New("can't get outputs from terraform apply logs") +var ( + errOutputFromApply = errors.New("can't get outputs from terraform apply logs") + errAlreadyRunning = errors.New("already running") +) type OutputJSON struct { Outputs map[string]*tfjson.StateOutput `json:"outputs"` @@ -35,6 +38,7 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmd *execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { @@ -49,21 +53,24 @@ func (r *Runner) CmdPath() string { } func (r *Runner) Init() error { + if r.cmd != nil { + return errAlreadyRunning + } + args := []string{"init"} if execx.NoTTY { args = append(args, "-no-color") } - err := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }).Run() - if err != nil { + r.cmd = r.initCmd(args) + + if err := r.cmd.Run(); err != nil { return fmt.Errorf("command execution failed: %w", err) } + r.cmd = nil + return nil } @@ -88,7 +95,7 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { err := os.WriteFile(path.Join(r.paths.Plan, fmt.Sprintf("plan-%d.log", timestamp)), - cmd.Log.Out.Bytes(), + r.cmd.Log.Out.Bytes(), iox.FullRWPermAccess) if err != nil { return out, fmt.Errorf("error writing terraform plan log: %w", err) @@ -102,18 +109,21 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { var oj OutputJSON - cmd := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: []string{"apply", "-no-color", "-json", "plan/terraform.plan"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }) - if err := cmd.Run(); err != nil { + if r.cmd != nil { + return oj, errAlreadyRunning + } + + args := []string{"apply", "-no-color", "-json", "plan/terraform.plan"} + + r.cmd = r.initCmd(args) + + if err := r.cmd.Run(); err != nil { return oj, fmt.Errorf("cannot create cloud resources: %w", err) } err := os.WriteFile(path.Join(r.paths.Logs, fmt.Sprintf("%d.log", timestamp)), - cmd.Log.Out.Bytes(), + r.cmd.Log.Out.Bytes(), iox.FullRWPermAccess) if err != nil { return oj, fmt.Errorf("error writing terraform apply log: %w", err) @@ -144,37 +154,69 @@ func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { return oj, fmt.Errorf("error writing terraform apply outputs: %w", err) } + r.cmd = nil + return oj, nil } func (r *Runner) Destroy() error { + if r.cmd != nil { + return errAlreadyRunning + } + args := []string{"destroy", "-auto-approve"} if execx.NoTTY { args = append(args, "-no-color") } - err := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }).Run() - if err != nil { + r.cmd = r.initCmd(args) + + if err := r.cmd.Run(); err != nil { return fmt.Errorf("error running terraform destroy: %w", err) } + r.cmd = nil + return nil } func (r *Runner) Version() (string, error) { - log, err := execx.CombinedOutput(execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: []string{"version"}, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + if r.cmd != nil { + return "", errAlreadyRunning + } + + args := []string{"version"} + + r.cmd = r.initCmd(args) + + log, err := execx.CombinedOutput(r.cmd) if err != nil { return "", fmt.Errorf("error running terraform version: %w", err) } + r.cmd = nil + return log, nil } + +func (r *Runner) Stop() error { + if r.cmd == nil { + return nil + } + + if err := r.cmd.Stop(); err != nil { + fmt.Println("Error stopping terraform runner") + return fmt.Errorf("error stopping terraform runner: %w", err) + } + + return nil +} + +func (r *Runner) initCmd(args []string) *execx.Cmd { + return execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Executor: r.executor, + WorkDir: r.paths.WorkDir, + Args: args, + }) +} diff --git a/internal/x/exec/cmd.go b/internal/x/exec/cmd.go index c2a6f1c2c..27001f4e7 100644 --- a/internal/x/exec/cmd.go +++ b/internal/x/exec/cmd.go @@ -101,6 +101,22 @@ func (c *Cmd) Run() error { return nil } +func (c *Cmd) Stop() error { + if c.Process == nil { + return nil + } + + // check if process is already exited + if c.ProcessState != nil && c.ProcessState.Exited() { + return nil + } + + if err := c.Process.Signal(os.Interrupt); err != nil { + return fmt.Errorf("failed to kill process: %w", err) + } + return nil +} + func (c *Cmd) RunWithTimeout(timeout time.Duration) error { var cmdCtx *exec.Cmd diff --git a/internal/x/exec/cmd_test.go b/internal/x/exec/cmd_test.go index 1e9caa44a..eaa1a5dbe 100644 --- a/internal/x/exec/cmd_test.go +++ b/internal/x/exec/cmd_test.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build unit - package execx_test import ( @@ -126,6 +124,41 @@ func Test_Cmd_Run(t *testing.T) { } } +func Test_Cmd_Stop(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + cmd *execx.Cmd + wantErr bool + }{ + { + desc: "succesful stop", + cmd: execx.NewCmd("long process", execx.CmdOptions{ + Args: []string{"sleep", "60"}, + Executor: execx.NewFakeExecutor(), + }), + wantErr: false, + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + err := tC.cmd.Stop() + + if (err != nil) != tC.wantErr { + t.Errorf("Cmd.Stop() error = %v, wantErr = %v", err, tC.wantErr) + } + + if tC.wantErr && !errors.Is(err, execx.ErrCmdFailed) { + t.Errorf("Cmd.Err = %v, want = %v", tC.cmd.Err, execx.ErrCmdFailed) + } + }) + } +} func Test_CmdLog_String(t *testing.T) { cmdLog := &execx.CmdLog{ Out: bytes.NewBufferString("foo"), From 1cd5251e97d77c871639390e02a9c6f796a245ae Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 26 Apr 2023 13:04:38 +0200 Subject: [PATCH 288/383] feat: rewrite runners --- .../kfd/v1alpha2/eks/create/distribution.go | 17 ++- .../kfd/v1alpha2/eks/create/infrastructure.go | 45 ++----- .../kfd/v1alpha2/eks/create/kubernetes.go | 10 +- internal/apis/kfd/v1alpha2/eks/creator.go | 1 - internal/tool/ansible/runner.go | 38 ++++-- internal/tool/awscli/runner.go | 70 +++++++---- internal/tool/furyagent/runner.go | 46 ++++--- internal/tool/kubectl/runner.go | 113 ++++++++++-------- internal/tool/kustomize/runner.go | 45 +++++-- internal/tool/openvpn/runner.go | 50 +++++--- internal/tool/terraform/runner.go | 100 +++++++--------- internal/x/exec/cmd_test.go | 1 + 12 files changed, 306 insertions(+), 230 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index b6ebb2cdf..558f63d5a 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -237,7 +237,6 @@ func (d *Distribution) Exec() error { } func (d *Distribution) Stop() []error { - // use gorooutines to stop all the tools var wg sync.WaitGroup errChan := make(chan error, 1) @@ -245,22 +244,31 @@ func (d *Distribution) Stop() []error { go func() { defer wg.Done() + + logrus.Debug("Stopping terraform...") + if err := d.tfRunner.Stop(); err != nil { - errChan <- err + errChan <- fmt.Errorf("error stopping terraform: %w", err) } }() go func() { defer wg.Done() + + logrus.Debug("Stopping kustomize...") + if err := d.kzRunner.Stop(); err != nil { - errChan <- err + errChan <- fmt.Errorf("error stopping kustomize: %w", err) } }() go func() { defer wg.Done() + + logrus.Debug("Stopping kubectl...") + if err := d.kubeRunner.Stop(); err != nil { - errChan <- err + errChan <- fmt.Errorf("error stopping kubectl: %w", err) } }() @@ -269,7 +277,6 @@ func (d *Distribution) Stop() []error { close(errChan) errs := make([]error, 0) - for err := range errChan { errs = append(errs, err) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 50998f8d4..111e7f826 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -11,7 +11,6 @@ import ( "fmt" "io/fs" "os" - "os/exec" "path" "strings" "sync" @@ -160,31 +159,37 @@ func (i *Infrastructure) Stop() []error { wg.Add(3) if i.ovRunner != nil { - fmt.Println("Stopping openvpn...") go func() { defer wg.Done() + + logrus.Debug("Stopping openvpn...") + if err := i.ovRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping openvpn: %v", err) + errChan <- fmt.Errorf("error stopping openvpn: %w", err) } }() } if i.faRunner != nil { - fmt.Println("Stopping furyagent...") go func() { defer wg.Done() + + logrus.Debug("Stopping furyagent...") + if err := i.faRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping furyagent: %v", err) + errChan <- fmt.Errorf("error stopping furyagent: %w", err) } }() } if i.tfRunner != nil { - fmt.Println("Stopping terraform...") go func() { defer wg.Done() + + logrus.Debug("Stopping terraform...") + if err := i.tfRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping terraform: %v", err) + errChan <- fmt.Errorf("error stopping terraform: %w", err) } }() } @@ -194,7 +199,6 @@ func (i *Infrastructure) Stop() []error { close(errChan) errs := make([]error, 0) - for err := range errChan { errs = append(errs, err) } @@ -202,31 +206,6 @@ func (i *Infrastructure) Stop() []error { return errs } -func (i *Infrastructure) isVpnConfigured() bool { - vpn := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn - if vpn == nil { - return false - } - - instances := i.furyctlConf.Spec.Infrastructure.Vpc.Vpn.Instances - if instances == nil { - return true - } - - return *instances > 0 -} - -func (i *Infrastructure) generateClientName() (string, error) { - whoamiResp, err := exec.Command("whoami").Output() - if err != nil { - return "", fmt.Errorf("error getting current user: %w", err) - } - - whoami := strings.TrimSpace(string(whoamiResp)) - - return fmt.Sprintf("%s-%s", i.furyctlConf.Metadata.Name, whoami), nil -} - func (i *Infrastructure) copyFromTemplate() error { var cfg template.Config diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 9fcf07e90..88b4cb93b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -263,7 +263,6 @@ func (*Kubernetes) getCommonDataFromDistribution(furyctlCfg template.Config) (ma } func (k *Kubernetes) Stop() []error { - // use goroutines to stop runners var wg sync.WaitGroup errChan := make(chan error, 1) @@ -272,16 +271,20 @@ func (k *Kubernetes) Stop() []error { go func() { defer wg.Done() + logrus.Debug("Stopping terraform...") + if err := k.tfRunner.Stop(); err != nil { - errChan <- err + errChan <- fmt.Errorf("error stopping terraform: %w", err) } }() go func() { defer wg.Done() + logrus.Debug("Stopping awscli...") + if err := k.awsRunner.Stop(); err != nil { - errChan <- err + errChan <- fmt.Errorf("error stopping awscli: %w", err) } }() @@ -290,7 +293,6 @@ func (k *Kubernetes) Stop() []error { close(errChan) errs := make([]error, 0) - for err := range errChan { errs = append(errs, err) } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 1ebf67533..448ee6801 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -247,7 +247,6 @@ func (v *ClusterCreator) Create(skipPhase string, timeout int) error { default: errCh <- ErrUnsupportedPhase } - }(execCloseCh, errCh) strBuilder := strings.Builder{} diff --git a/internal/tool/ansible/runner.go b/internal/tool/ansible/runner.go index 519c51a23..9b828521a 100644 --- a/internal/tool/ansible/runner.go +++ b/internal/tool/ansible/runner.go @@ -7,6 +7,8 @@ package ansible import ( "fmt" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -18,17 +20,13 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, - cmd: execx.NewCmd(paths.Ansible, execx.CmdOptions{ - Executor: executor, - WorkDir: paths.WorkDir, - }), } } @@ -36,12 +34,30 @@ func (r *Runner) CmdPath() string { return r.paths.Ansible } +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Ansible, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + func (r *Runner) Version() (string, error) { - args := []string{r.paths.Ansible, "--version"} + args := []string{"--version"} - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting ansible version: %w", err) } @@ -50,8 +66,10 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Stop() error { - if err := r.cmd.Stop(); err != nil { - return fmt.Errorf("error stopping ansible runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping ansible runner: %w", err) + } } return nil diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index d09437abf..91aca437d 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -7,6 +7,8 @@ package awscli import ( "fmt" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -18,17 +20,13 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, - cmd: execx.NewCmd(paths.Awscli, execx.CmdOptions{ - Executor: executor, - WorkDir: paths.WorkDir, - }), } } @@ -36,16 +34,34 @@ func (r *Runner) CmdPath() string { return r.paths.Awscli } +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + func (r *Runner) Ec2(sub string, params ...string) (string, error) { - args := []string{r.paths.Awscli, "ec2", sub} + args := []string{"ec2", sub} if len(params) > 0 { args = append(args, params...) } - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error running awscli ec2 %s: %w", sub, err) } @@ -57,11 +73,10 @@ func (r *Runner) S3(params ...string) (string, error) { args := []string{"s3"} args = append(args, params...) - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error executing awscli s3: %w", err) } @@ -70,12 +85,13 @@ func (r *Runner) S3(params ...string) (string, error) { } func (r *Runner) S3Api(params ...string) (string, error) { - args := []string{r.paths.Awscli, "s3api"} + args := []string{"s3api"} args = append(args, params...) - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error executing awscli s3api: %w", err) } @@ -90,11 +106,10 @@ func (r *Runner) Route53(sub string, params ...string) (string, error) { args = append(args, params...) } - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Awscli, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error running awscli ec2 %s: %w", sub, err) } @@ -103,11 +118,12 @@ func (r *Runner) Route53(sub string, params ...string) (string, error) { } func (r *Runner) Version() (string, error) { - args := []string{r.paths.Awscli, "--version"} + args := []string{"--version"} - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting awscli version: %w", err) } @@ -116,8 +132,10 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Stop() error { - if err := r.cmd.Stop(); err != nil { - return fmt.Errorf("error stopping awscli runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping awscli runner: %w", err) + } } return nil diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index 3f1fdc182..4e2ee46b5 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -8,6 +8,8 @@ import ( "bytes" "fmt" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -19,17 +21,13 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, - cmd: execx.NewCmd(paths.Furyagent, execx.CmdOptions{ - Executor: executor, - WorkDir: paths.WorkDir, - }), } } @@ -37,30 +35,48 @@ func (r *Runner) CmdPath() string { return r.paths.Furyagent } +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Furyagent, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + func (r *Runner) ConfigOpenvpnClient(name string, params ...string) (*bytes.Buffer, error) { args := []string{ - r.paths.Furyagent, "configure", "openvpn-client", fmt.Sprintf("--client-name=%s", name), "--config=furyagent.yml", } - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - if err := r.cmd.Run(); err != nil { + if err := cmd.Run(); err != nil { return nil, fmt.Errorf("error while running furyagent configure openvpn-client: %w", err) } - return r.cmd.Log.Out, nil + return cmd.Log.Out, nil } func (r *Runner) Version() (string, error) { - args := []string{r.paths.Furyagent, "version"} + args := []string{"version"} - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting furyagent version: %w", err) } @@ -69,8 +85,10 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Stop() error { - if err := r.cmd.Stop(); err != nil { - return fmt.Errorf("error stopping furyagent runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping furyagent runner: %w", err) + } } return nil diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index a95816fa6..acfaa3080 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -8,6 +8,8 @@ import ( "fmt" "time" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -24,10 +26,10 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd serverSide bool skipNotFound bool clientVersion bool + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound, clientVersion bool) *Runner { @@ -37,10 +39,6 @@ func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound, c serverSide: serverSide, skipNotFound: skipNotFound, clientVersion: clientVersion, - cmd: execx.NewCmd(paths.Kubectl, execx.CmdOptions{ - Executor: executor, - WorkDir: paths.WorkDir, - }), } } @@ -48,8 +46,25 @@ func (r *Runner) CmdPath() string { return r.paths.Kubectl } +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + func (r *Runner) Apply(manifestPath string, params ...string) error { - args := []string{r.paths.Kubectl, "apply"} + args := []string{"apply"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -65,9 +80,10 @@ func (r *Runner) Apply(manifestPath string, params ...string) error { args = append(args, "-f", manifestPath) - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - if _, err := execx.CombinedOutput(r.cmd); err != nil { + if _, err := execx.CombinedOutput(cmd); err != nil { return fmt.Errorf("error applying manifests: %w", err) } @@ -75,7 +91,7 @@ func (r *Runner) Apply(manifestPath string, params ...string) error { } func (r *Runner) Get(ns string, params ...string) (string, error) { - args := []string{r.paths.Kubectl, "get"} + args := []string{"get"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -89,9 +105,10 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { args = append(args, params...) - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return out, fmt.Errorf("error while getting resources: %w", err) } @@ -100,10 +117,7 @@ func (r *Runner) Get(ns string, params ...string) (string, error) { } func (r *Runner) APIResources(params ...string) (string, error) { - args := []string{ - r.paths.Kubectl, - "api-resources", - } + args := []string{"api-resources"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -111,11 +125,10 @@ func (r *Runner) APIResources(params ...string) (string, error) { args = append(args, params...) - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return out, fmt.Errorf("error while listing api resources: %w", err) } @@ -130,9 +143,10 @@ func (r *Runner) GetResource(ns, res, name string) (string, error) { args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return out, fmt.Errorf("error while getting resources: %w", err) } @@ -148,11 +162,10 @@ func (r *Runner) DeleteResource(ns, res, name string) (string, error) { args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return out, fmt.Errorf("error deleting resource(s) \"%s/%s/%s\": %w", ns, res, name, err) } @@ -168,11 +181,10 @@ func (r *Runner) DeleteResources(ns, res string) (string, error) { args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return out, fmt.Errorf("error deleting resource(s) \"%s/%s\": %w", ns, res, err) } @@ -188,11 +200,10 @@ func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { args = append(args, "--kubeconfig", r.paths.Kubeconfig) } - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Kubectl, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - })) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return out, fmt.Errorf("error deleting all \"%s\" resources in all namespaces: %w", res, err) } @@ -201,10 +212,7 @@ func (r *Runner) DeleteResourcesInAllNamespaces(res string) (string, error) { } func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { - args := []string{ - r.paths.Kubectl, - "delete", - } + args := []string{"delete"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -220,19 +228,19 @@ func (r *Runner) Delete(manifestPath string, params ...string) (string, error) { args = append(args, "-f", manifestPath) - r.cmd.Args = args - - _, err := execx.CombinedOutputWithTimeout(r.cmd, kubectlDeleteTimeout) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + out, err := execx.CombinedOutputWithTimeout(cmd, kubectlDeleteTimeout) if err != nil { - return res, fmt.Errorf("error deleting resources: %w", err) + return out, fmt.Errorf("error deleting resources: %w", err) } - return res, nil + return out, nil } func (r *Runner) Version() (string, error) { - args := []string{r.paths.Kubectl, "version"} + args := []string{"version"} if r.paths.Kubeconfig != "" { args = append(args, "--kubeconfig", r.paths.Kubeconfig) @@ -244,9 +252,10 @@ func (r *Runner) Version() (string, error) { args = append(args, "-o", "json") - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting kubectl version: %w", err) } @@ -255,8 +264,10 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Stop() error { - if err := r.cmd.Stop(); err != nil { - return fmt.Errorf("error stopping kubectl runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping kubectl runner: %w", err) + } } return nil diff --git a/internal/tool/kustomize/runner.go b/internal/tool/kustomize/runner.go index 6e3162d9b..14c40d391 100644 --- a/internal/tool/kustomize/runner.go +++ b/internal/tool/kustomize/runner.go @@ -7,6 +7,8 @@ package kustomize import ( "fmt" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -18,17 +20,13 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, - cmd: execx.NewCmd(paths.Kustomize, execx.CmdOptions{ - Executor: executor, - WorkDir: paths.WorkDir, - }), } } @@ -36,12 +34,30 @@ func (r *Runner) CmdPath() string { return r.paths.Kustomize } +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Kustomize, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + func (r *Runner) Version() (string, error) { - args := []string{r.paths.Kustomize, "version", "--short"} + args := []string{"version", "--short"} - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting kustomize version: %w", err) } @@ -50,11 +66,12 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Build() (string, error) { - args := []string{r.paths.Kustomize, "build", "--load_restrictor", "none", "."} + args := []string{"build", "--load_restrictor", "none", "."} - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error while running kustomize build: %w", err) } @@ -63,8 +80,10 @@ func (r *Runner) Build() (string, error) { } func (r *Runner) Stop() error { - if err := r.cmd.Stop(); err != nil { - return fmt.Errorf("error stopping kustomize runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping kustomize runner: %w", err) + } } return nil diff --git a/internal/tool/openvpn/runner.go b/internal/tool/openvpn/runner.go index 04a334084..004b43905 100644 --- a/internal/tool/openvpn/runner.go +++ b/internal/tool/openvpn/runner.go @@ -7,6 +7,8 @@ package openvpn import ( "fmt" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" osx "github.com/sighupio/furyctl/internal/x/os" ) @@ -19,17 +21,13 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, - cmd: execx.NewCmd(paths.Openvpn, execx.CmdOptions{ - Executor: executor, - WorkDir: paths.WorkDir, - }), } } @@ -37,9 +35,30 @@ func (r *Runner) CmdPath() string { return r.paths.Openvpn } +func (r *Runner) newCmdWithPath(path string, args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(path, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + return r.newCmdWithPath(r.paths.Openvpn, args) +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + func (r *Runner) Connect(name string) error { path := "sudo" - args := []string{r.paths.Openvpn, "--config", fmt.Sprintf("%s.ovpn", name), "--daemon"} + args := []string{"--config", fmt.Sprintf("%s.ovpn", name), "--daemon"} userIsRoot, err := osx.IsRoot() if err != nil { @@ -51,10 +70,10 @@ func (r *Runner) Connect(name string) error { args = args[1:] } - r.cmd.Args = args - r.cmd.Path = path + cmd, id := r.newCmdWithPath(path, args) + defer r.deleteCmd(id) - if err := r.cmd.Run(); err != nil { + if err := cmd.Run(); err != nil { return fmt.Errorf("error while running openvpn: %w", err) } @@ -62,11 +81,12 @@ func (r *Runner) Connect(name string) error { } func (r *Runner) Version() (string, error) { - args := []string{r.paths.Openvpn, "--version"} + args := []string{"--version"} - r.cmd.Args = args + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - out, err := execx.CombinedOutput(r.cmd) + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting openvpn version: %w", err) } @@ -75,8 +95,10 @@ func (r *Runner) Version() (string, error) { } func (r *Runner) Stop() error { - if err := r.cmd.Stop(); err != nil { - return fmt.Errorf("error stopping openvpn runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping openvpn runner: %w", err) + } } return nil diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index b0f02e8c5..c4d73c1b4 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -12,16 +12,14 @@ import ( "path" "regexp" + "github.com/google/uuid" tfjson "github.com/hashicorp/terraform-json" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" ) -var ( - errOutputFromApply = errors.New("can't get outputs from terraform apply logs") - errAlreadyRunning = errors.New("already running") -) +var errOutputFromApply = errors.New("can't get outputs from terraform apply logs") type OutputJSON struct { Outputs map[string]*tfjson.StateOutput `json:"outputs"` @@ -38,7 +36,7 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths - cmd *execx.Cmd + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { @@ -52,25 +50,37 @@ func (r *Runner) CmdPath() string { return r.paths.Terraform } -func (r *Runner) Init() error { - if r.cmd != nil { - return errAlreadyRunning - } +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ + Args: args, + Executor: r.executor, + WorkDir: r.paths.WorkDir, + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + +func (r *Runner) Init() error { args := []string{"init"} if execx.NoTTY { args = append(args, "-no-color") } - r.cmd = r.initCmd(args) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - if err := r.cmd.Run(); err != nil { + if err := cmd.Run(); err != nil { return fmt.Errorf("command execution failed: %w", err) } - r.cmd = nil - return nil } @@ -84,18 +94,16 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { args = append(args, "-no-color", "-out", "plan/terraform.plan") - cmd := execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Args: args, - Executor: r.executor, - WorkDir: r.paths.WorkDir, - }) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) + if err := cmd.Run(); err != nil { return out, fmt.Errorf("command execution failed: %w", err) } err := os.WriteFile(path.Join(r.paths.Plan, fmt.Sprintf("plan-%d.log", timestamp)), - r.cmd.Log.Out.Bytes(), + cmd.Log.Out.Bytes(), iox.FullRWPermAccess) if err != nil { return out, fmt.Errorf("error writing terraform plan log: %w", err) @@ -109,21 +117,18 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { var oj OutputJSON - if r.cmd != nil { - return oj, errAlreadyRunning - } - args := []string{"apply", "-no-color", "-json", "plan/terraform.plan"} - r.cmd = r.initCmd(args) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - if err := r.cmd.Run(); err != nil { + if err := cmd.Run(); err != nil { return oj, fmt.Errorf("cannot create cloud resources: %w", err) } err := os.WriteFile(path.Join(r.paths.Logs, fmt.Sprintf("%d.log", timestamp)), - r.cmd.Log.Out.Bytes(), + cmd.Log.Out.Bytes(), iox.FullRWPermAccess) if err != nil { return oj, fmt.Errorf("error writing terraform apply log: %w", err) @@ -154,69 +159,46 @@ func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { return oj, fmt.Errorf("error writing terraform apply outputs: %w", err) } - r.cmd = nil - return oj, nil } func (r *Runner) Destroy() error { - if r.cmd != nil { - return errAlreadyRunning - } - args := []string{"destroy", "-auto-approve"} if execx.NoTTY { args = append(args, "-no-color") } - r.cmd = r.initCmd(args) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - if err := r.cmd.Run(); err != nil { + if err := cmd.Run(); err != nil { return fmt.Errorf("error running terraform destroy: %w", err) } - r.cmd = nil - return nil } func (r *Runner) Version() (string, error) { - if r.cmd != nil { - return "", errAlreadyRunning - } - args := []string{"version"} - r.cmd = r.initCmd(args) + cmd, id := r.newCmd(args) + defer r.deleteCmd(id) - log, err := execx.CombinedOutput(r.cmd) + log, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error running terraform version: %w", err) } - r.cmd = nil - return log, nil } func (r *Runner) Stop() error { - if r.cmd == nil { - return nil - } - - if err := r.cmd.Stop(); err != nil { - fmt.Println("Error stopping terraform runner") - return fmt.Errorf("error stopping terraform runner: %w", err) + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping terraform runner: %w", err) + } } return nil } - -func (r *Runner) initCmd(args []string) *execx.Cmd { - return execx.NewCmd(r.paths.Terraform, execx.CmdOptions{ - Executor: r.executor, - WorkDir: r.paths.WorkDir, - Args: args, - }) -} diff --git a/internal/x/exec/cmd_test.go b/internal/x/exec/cmd_test.go index eaa1a5dbe..08c1b014f 100644 --- a/internal/x/exec/cmd_test.go +++ b/internal/x/exec/cmd_test.go @@ -159,6 +159,7 @@ func Test_Cmd_Stop(t *testing.T) { }) } } + func Test_CmdLog_String(t *testing.T) { cmdLog := &execx.CmdLog{ Out: bytes.NewBufferString("foo"), From 63f61d3cd6046307cec8864f4f809135c5ceab6e Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 27 Apr 2023 12:21:31 +0200 Subject: [PATCH 289/383] feat: refactor stop functions --- .../kfd/v1alpha2/eks/create/distribution.go | 43 ++++++----- .../kfd/v1alpha2/eks/create/infrastructure.go | 72 ++++++------------- .../kfd/v1alpha2/eks/create/kubernetes.go | 37 +++++----- internal/tool/runner_test.go | 2 + internal/x/exec/cmd.go | 4 +- internal/x/exec/cmd_test.go | 1 + 6 files changed, 70 insertions(+), 89 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 558f63d5a..9b0f75aa6 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -236,52 +236,57 @@ func (d *Distribution) Exec() error { return d.applyManifests(manifestsOutPath) } -func (d *Distribution) Stop() []error { - var wg sync.WaitGroup - errChan := make(chan error, 1) +func (d *Distribution) Stop() error { + errCh := make(chan error) + doneCh := make(chan bool) + var wg sync.WaitGroup wg.Add(3) go func() { - defer wg.Done() - logrus.Debug("Stopping terraform...") if err := d.tfRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping terraform: %w", err) + errCh <- fmt.Errorf("error stopping terraform: %w", err) } + + wg.Done() }() go func() { - defer wg.Done() - logrus.Debug("Stopping kustomize...") if err := d.kzRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping kustomize: %w", err) + errCh <- fmt.Errorf("error stopping kustomize: %w", err) } + + wg.Done() }() go func() { - defer wg.Done() - logrus.Debug("Stopping kubectl...") if err := d.kubeRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping kubectl: %w", err) + errCh <- fmt.Errorf("error stopping kubectl: %w", err) } - }() - wg.Wait() + wg.Done() + }() - close(errChan) + go func() { + wg.Wait() + close(doneCh) + }() - errs := make([]error, 0) - for err := range errChan { - errs = append(errs, err) + select { + case <-doneCh: + break + case err := <-errCh: + close(errCh) + return err } - return errs + return nil } func (d *Distribution) createFuryctlMerger() (*merge.Merger, error) { diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 111e7f826..f0d0c2de6 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -13,7 +13,6 @@ import ( "os" "path" "strings" - "sync" "time" "github.com/sirupsen/logrus" @@ -46,7 +45,9 @@ type Infrastructure struct { furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD tfRunner *terraform.Runner - dryRun bool + // ovRunner *openvpn.Runner + // faRunner *furyagent.Runner + dryRun bool } func NewInfrastructure( @@ -78,6 +79,19 @@ func NewInfrastructure( Terraform: phase.TerraformPath, }, ), + // ovRunner: openvpn.NewRunner( + // executor, + // openvpn.Paths{ + // WorkDir: path.Join(phase.Path, "terraform"), + // Openvpn: paths., + // }, + // ), + // faRunner: furyagent.NewRunner( + // executor, + // furyagent.Paths{ + // WorkDir: path.Join(phase.Path, "terraform"), + // }, + // ), dryRun: dryRun, }, nil } @@ -152,58 +166,12 @@ func (i *Infrastructure) Exec() error { return nil } -func (i *Infrastructure) Stop() []error { - var wg sync.WaitGroup - errChan := make(chan error, 1) - - wg.Add(3) - - if i.ovRunner != nil { - go func() { - defer wg.Done() - - logrus.Debug("Stopping openvpn...") - - if err := i.ovRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping openvpn: %w", err) - } - }() +func (i *Infrastructure) Stop() error { + if err := i.tfRunner.Stop(); err != nil { + return fmt.Errorf("error stopping terraform: %w", err) } - if i.faRunner != nil { - go func() { - defer wg.Done() - - logrus.Debug("Stopping furyagent...") - - if err := i.faRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping furyagent: %w", err) - } - }() - } - - if i.tfRunner != nil { - go func() { - defer wg.Done() - - logrus.Debug("Stopping terraform...") - - if err := i.tfRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping terraform: %w", err) - } - }() - } - - wg.Wait() - - close(errChan) - - errs := make([]error, 0) - for err := range errChan { - errs = append(errs, err) - } - - return errs + return nil } func (i *Infrastructure) copyFromTemplate() error { diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 88b4cb93b..10aaab349 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -262,42 +262,47 @@ func (*Kubernetes) getCommonDataFromDistribution(furyctlCfg template.Config) (ma return nodeSelector, tolerations, nil } -func (k *Kubernetes) Stop() []error { - var wg sync.WaitGroup - errChan := make(chan error, 1) +func (k *Kubernetes) Stop() error { + errCh := make(chan error) + doneCh := make(chan bool) + var wg sync.WaitGroup wg.Add(2) go func() { - defer wg.Done() - logrus.Debug("Stopping terraform...") if err := k.tfRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping terraform: %w", err) + errCh <- fmt.Errorf("error stopping terraform: %w", err) } + + wg.Done() }() go func() { - defer wg.Done() - logrus.Debug("Stopping awscli...") if err := k.awsRunner.Stop(); err != nil { - errChan <- fmt.Errorf("error stopping awscli: %w", err) + errCh <- fmt.Errorf("error stopping awscli: %w", err) } - }() - wg.Wait() + wg.Done() + }() - close(errChan) + go func() { + wg.Wait() + close(doneCh) + }() - errs := make([]error, 0) - for err := range errChan { - errs = append(errs, err) + select { + case <-doneCh: + break + case err := <-errCh: + close(errCh) + return err } - return errs + return nil } func (k *Kubernetes) copyFromTemplate(furyctlCfg template.Config) error { diff --git a/internal/tool/runner_test.go b/internal/tool/runner_test.go index ebf9ad322..2612f9833 100644 --- a/internal/tool/runner_test.go +++ b/internal/tool/runner_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build unit + package tool_test import ( diff --git a/internal/x/exec/cmd.go b/internal/x/exec/cmd.go index 27001f4e7..761aa6302 100644 --- a/internal/x/exec/cmd.go +++ b/internal/x/exec/cmd.go @@ -106,14 +106,14 @@ func (c *Cmd) Stop() error { return nil } - // check if process is already exited if c.ProcessState != nil && c.ProcessState.Exited() { return nil } if err := c.Process.Signal(os.Interrupt); err != nil { - return fmt.Errorf("failed to kill process: %w", err) + return fmt.Errorf("failed to interrupt process: %w", err) } + return nil } diff --git a/internal/x/exec/cmd_test.go b/internal/x/exec/cmd_test.go index 08c1b014f..50305fd02 100644 --- a/internal/x/exec/cmd_test.go +++ b/internal/x/exec/cmd_test.go @@ -141,6 +141,7 @@ func Test_Cmd_Stop(t *testing.T) { wantErr: false, }, } + for _, tC := range testCases { tC := tC From 11370d008784101c9beeedd28340e0caf8488794 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 27 Apr 2023 12:22:31 +0200 Subject: [PATCH 290/383] doc: remove comments --- .../kfd/v1alpha2/eks/create/infrastructure.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index f0d0c2de6..b7dae4069 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -45,9 +45,7 @@ type Infrastructure struct { furyctlConf private.EksclusterKfdV1Alpha2 kfdManifest config.KFD tfRunner *terraform.Runner - // ovRunner *openvpn.Runner - // faRunner *furyagent.Runner - dryRun bool + dryRun bool } func NewInfrastructure( @@ -79,19 +77,6 @@ func NewInfrastructure( Terraform: phase.TerraformPath, }, ), - // ovRunner: openvpn.NewRunner( - // executor, - // openvpn.Paths{ - // WorkDir: path.Join(phase.Path, "terraform"), - // Openvpn: paths., - // }, - // ), - // faRunner: furyagent.NewRunner( - // executor, - // furyagent.Paths{ - // WorkDir: path.Join(phase.Path, "terraform"), - // }, - // ), dryRun: dryRun, }, nil } From 02d1953b1e9fc5c77575beff8d05b77e4b858ad7 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 27 Apr 2023 12:47:21 +0200 Subject: [PATCH 291/383] feat: implement timeout on eks creator --- .../kfd/v1alpha2/eks/create/infrastructure.go | 2 + internal/apis/kfd/v1alpha2/eks/creator.go | 180 +++++------------- internal/tool/ansible/runner.go | 1 + internal/tool/awscli/runner.go | 1 + internal/tool/furyagent/runner.go | 1 + internal/tool/kubectl/runner.go | 1 + internal/tool/kustomize/runner.go | 1 + internal/tool/openvpn/runner.go | 1 + internal/tool/terraform/runner.go | 1 + 9 files changed, 52 insertions(+), 137 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index b7dae4069..0a3f3ed34 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -152,6 +152,8 @@ func (i *Infrastructure) Exec() error { } func (i *Infrastructure) Stop() error { + logrus.Debug("Stopping terraform...") + if err := i.tfRunner.Stop(); err != nil { return fmt.Errorf("error stopping terraform: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 448ee6801..ce7042e43 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -11,7 +11,6 @@ import ( "path" "path/filepath" "strings" - "sync" "time" "github.com/sirupsen/logrus" @@ -29,7 +28,6 @@ import ( var ( ErrUnsupportedPhase = errors.New("unsupported phase") ErrInfraNotPresent = errors.New("the configuration file does not contain an infrastructure section") - ErrClusterCreation = errors.New("cluster creation failed") ErrTimeout = errors.New("timeout reached") ) @@ -136,169 +134,77 @@ func (v *ClusterCreator) Create(skipPhase string, timeout int) error { } errCh := make(chan error) - execCloseCh := make(chan bool) - var wg sync.WaitGroup - - wg.Add(1) - - go func(closeCh chan bool, errChan chan error) { - defer func() { - closeCh <- true - }() + doneCh := make(chan bool) + go func() { switch v.phase { case cluster.OperationPhaseInfrastructure: - if v.furyctlConf.Spec.Infrastructure == nil { - absPath, err := filepath.Abs(v.paths.ConfigPath) - if err != nil { - logrus.Debugf("error while getting absolute path of %s: %v", v.paths.ConfigPath, err) - - errCh <- fmt.Errorf("%w: at %s", ErrInfraNotPresent, v.paths.ConfigPath) - - return - } - - errCh <- fmt.Errorf("%w: check at %s", ErrInfraNotPresent, absPath) - - return + if err := v.infraPhase(infra, vpnConnector); err != nil { + errCh <- err } - - if err = infra.Exec(infraOpts); err != nil { - errCh <- fmt.Errorf("error while executing infrastructure phase: %w", err) - - return - } - - if v.dryRun { - logrus.Info("Infrastructure created successfully (dry-run mode)") - } - - logrus.Info("Infrastructure created successfully") - - if v.furyctlConf.Spec.Infrastructure != nil { - if v.furyctlConf.Spec.Infrastructure.Vpc.Vpn != nil && v.vpnAutoConnect { - logrus.Info("Please remember to kill the VPN connection when you finish doing operations on the cluster") - } - } - - break + close(doneCh) case cluster.OperationPhaseKubernetes: - logrus.Warn("Please make sure that the Kubernetes API is reachable before continuing" + - " (e.g. check VPN connection is active`), otherwise the installation will fail.") - - if err = kube.Exec(); err != nil { - errCh <- fmt.Errorf("error while executing kubernetes phase: %w", err) - - return - } - - if v.dryRun { - logrus.Info("Kubernetes cluster created successfully (dry-run mode)") - - return - } - - if err := v.storeClusterConfig(); err != nil { - errCh <- fmt.Errorf("error while creating secret with the cluster configuration: %w", err) - - return + if err := v.kubernetesPhase(kube, vpnConnector); err != nil { + errCh <- err } - - logrus.Info("Kubernetes cluster created successfully") - - err = v.logKubeconfig() - if err != nil { - errCh <- fmt.Errorf("error while logging kubeconfig path: %w", err) - - return - } - - break + close(doneCh) case cluster.OperationPhaseDistribution: - if err = distro.Exec(); err != nil { - errCh <- fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) - - return - } - - if v.dryRun { - logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") - - return - } - - if err := v.storeClusterConfig(); err != nil { - errCh <- fmt.Errorf("error while creating secret with the cluster configuration: %w", err) - - return + if err := v.distributionPhase(distro, vpnConnector); err != nil { + errCh <- err } - - logrus.Info("Kubernetes Fury Distribution installed successfully") - - break + close(doneCh) case cluster.OperationPhaseAll: - errCh <- v.allPhases(skipPhase, infraOpts, infra, kube, distro) - - return + errCh <- v.allPhases(skipPhase, infra, kube, distro, vpnConnector) + close(doneCh) default: errCh <- ErrUnsupportedPhase + close(doneCh) } - }(execCloseCh, errCh) - - strBuilder := strings.Builder{} - - for { - select { - case <-time.After(time.Duration(timeout) * time.Second): - fmt.Println("I'm select: timeout reached") - - fmt.Println("I'm select: stopping execChan") - wg.Done() - fmt.Println("I'm select: stopping errChan") - close(errCh) + }() - errs := make([]error, 0) - errs = append(errs, ErrTimeout) - - fmt.Println("I'm select: Waiting for all processes to stop...") - - if infra != nil { - errs = append(errs, infra.Stop()...) + select { + case <-time.After(time.Duration(timeout) * time.Second): + switch v.phase { + case cluster.OperationPhaseInfrastructure: + if err := infra.Stop(); err != nil { + return err } - if kube != nil { - errs = append(errs, kube.Stop()...) + case cluster.OperationPhaseKubernetes: + if err := kube.Stop(); err != nil { + return err } - if distro != nil { - errs = append(errs, distro.Stop()...) + case cluster.OperationPhaseDistribution: + if err := distro.Stop(); err != nil { + return err } - fmt.Println("I'm select: returning error") - - for _, err := range errs { - strBuilder.WriteString(err.Error()) - strBuilder.WriteString(" - ") + case cluster.OperationPhaseAll: + if err := infra.Stop(); err != nil { + return err + } + if err := kube.Stop(); err != nil { + return err + } + if err := distro.Stop(); err != nil { + return err } - return fmt.Errorf(strBuilder.String()) + } - case r := <-execCloseCh: - if r { - close(execCloseCh) - close(errCh) + return ErrTimeout - return fmt.Errorf(strBuilder.String()) - } + case <-doneCh: + break - case err := <-errCh: - strBuilder.WriteString(err.Error()) - strBuilder.WriteString(" - ") - } + case err := <-errCh: + close(errCh) + return err } return nil diff --git a/internal/tool/ansible/runner.go b/internal/tool/ansible/runner.go index 9b828521a..78da5ee8f 100644 --- a/internal/tool/ansible/runner.go +++ b/internal/tool/ansible/runner.go @@ -27,6 +27,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } diff --git a/internal/tool/awscli/runner.go b/internal/tool/awscli/runner.go index 91aca437d..f89cad70e 100644 --- a/internal/tool/awscli/runner.go +++ b/internal/tool/awscli/runner.go @@ -27,6 +27,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index 4e2ee46b5..12e5e6b69 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -28,6 +28,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } diff --git a/internal/tool/kubectl/runner.go b/internal/tool/kubectl/runner.go index acfaa3080..3ff6e1c6e 100644 --- a/internal/tool/kubectl/runner.go +++ b/internal/tool/kubectl/runner.go @@ -39,6 +39,7 @@ func NewRunner(executor execx.Executor, paths Paths, serverSide, skipNotFound, c serverSide: serverSide, skipNotFound: skipNotFound, clientVersion: clientVersion, + cmds: make(map[string]*execx.Cmd), } } diff --git a/internal/tool/kustomize/runner.go b/internal/tool/kustomize/runner.go index 14c40d391..860f2997a 100644 --- a/internal/tool/kustomize/runner.go +++ b/internal/tool/kustomize/runner.go @@ -27,6 +27,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } diff --git a/internal/tool/openvpn/runner.go b/internal/tool/openvpn/runner.go index 004b43905..30ea44ec9 100644 --- a/internal/tool/openvpn/runner.go +++ b/internal/tool/openvpn/runner.go @@ -28,6 +28,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index c4d73c1b4..196ed10e5 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -43,6 +43,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } From 55720b2770c46e29dde90ea57684a11f5b8d9b38 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 27 Apr 2023 12:57:54 +0200 Subject: [PATCH 292/383] fix: address linting issues --- cmd/create/cluster.go | 1 + .../kfd/v1alpha2/eks/create/distribution.go | 5 ++++- .../kfd/v1alpha2/eks/create/kubernetes.go | 5 ++++- internal/apis/kfd/v1alpha2/eks/creator.go | 22 ++++++++++++------- internal/tool/furyagent/runner.go | 2 +- internal/x/exec/cmd_test.go | 13 ++++++----- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 185c9e0ff..5b716c567 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -435,6 +435,7 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { "Path to the kubeconfig file, mandatory if you want to run the distribution phase alone and the KUBECONFIG environment variable is not set", ) + //nolint:gomnd,revive // ignore magic number linters cmd.Flags().Int( "timeout", 60, diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 9b0f75aa6..6786b5567 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -241,6 +241,8 @@ func (d *Distribution) Stop() error { doneCh := make(chan bool) var wg sync.WaitGroup + + //nolint:gomnd,revive // ignore magic number linters wg.Add(3) go func() { @@ -280,9 +282,10 @@ func (d *Distribution) Stop() error { select { case <-doneCh: - break + case err := <-errCh: close(errCh) + return err } diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 10aaab349..71b6c46c8 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -267,6 +267,8 @@ func (k *Kubernetes) Stop() error { doneCh := make(chan bool) var wg sync.WaitGroup + + //nolint:gomnd // ignore magic number linters wg.Add(2) go func() { @@ -296,9 +298,10 @@ func (k *Kubernetes) Stop() error { select { case <-doneCh: - break + case err := <-errCh: close(errCh) + return err } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index ce7042e43..af1459c21 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -142,26 +142,31 @@ func (v *ClusterCreator) Create(skipPhase string, timeout int) error { if err := v.infraPhase(infra, vpnConnector); err != nil { errCh <- err } + close(doneCh) case cluster.OperationPhaseKubernetes: if err := v.kubernetesPhase(kube, vpnConnector); err != nil { errCh <- err } + close(doneCh) case cluster.OperationPhaseDistribution: if err := v.distributionPhase(distro, vpnConnector); err != nil { errCh <- err } + close(doneCh) case cluster.OperationPhaseAll: errCh <- v.allPhases(skipPhase, infra, kube, distro, vpnConnector) + close(doneCh) default: errCh <- ErrUnsupportedPhase + close(doneCh) } }() @@ -171,39 +176,40 @@ func (v *ClusterCreator) Create(skipPhase string, timeout int) error { switch v.phase { case cluster.OperationPhaseInfrastructure: if err := infra.Stop(); err != nil { - return err + return fmt.Errorf("error stopping infrastructure phase: %w", err) } case cluster.OperationPhaseKubernetes: if err := kube.Stop(); err != nil { - return err + return fmt.Errorf("error stopping kubernetes phase: %w", err) } case cluster.OperationPhaseDistribution: if err := distro.Stop(); err != nil { - return err + return fmt.Errorf("error stopping distribution phase: %w", err) } case cluster.OperationPhaseAll: if err := infra.Stop(); err != nil { - return err + return fmt.Errorf("error stopping infrastructure phase: %w", err) } + if err := kube.Stop(); err != nil { - return err + return fmt.Errorf("error stopping kubernetes phase: %w", err) } + if err := distro.Stop(); err != nil { - return err + return fmt.Errorf("error stopping distribution phase: %w", err) } - } return ErrTimeout case <-doneCh: - break case err := <-errCh: close(errCh) + return err } diff --git a/internal/tool/furyagent/runner.go b/internal/tool/furyagent/runner.go index 12e5e6b69..3aaf5a648 100644 --- a/internal/tool/furyagent/runner.go +++ b/internal/tool/furyagent/runner.go @@ -53,7 +53,7 @@ func (r *Runner) deleteCmd(id string) { delete(r.cmds, id) } -func (r *Runner) ConfigOpenvpnClient(name string, params ...string) (*bytes.Buffer, error) { +func (r *Runner) ConfigOpenvpnClient(name string, _ ...string) (*bytes.Buffer, error) { args := []string{ "configure", "openvpn-client", diff --git a/internal/x/exec/cmd_test.go b/internal/x/exec/cmd_test.go index 50305fd02..d546465fd 100644 --- a/internal/x/exec/cmd_test.go +++ b/internal/x/exec/cmd_test.go @@ -16,8 +16,9 @@ import ( ) func TestNewErrCmdFailed(t *testing.T) { - err := execx.NewErrCmdFailed("foo", []string{"bar", "baz"}, errors.New("test error"), nil) + t.Parallel() + err := execx.NewErrCmdFailed("foo", []string{"bar", "baz"}, errors.New("test error"), nil) if err == nil { t.Error("error is nil") } @@ -100,7 +101,7 @@ func Test_Cmd_Run(t *testing.T) { wantErr: true, }, { - desc: "succesful run", + desc: "successful run", cmd: execx.NewCmd("true", execx.CmdOptions{}), wantErr: false, }, @@ -133,7 +134,7 @@ func Test_Cmd_Stop(t *testing.T) { wantErr bool }{ { - desc: "succesful stop", + desc: "successful stop", cmd: execx.NewCmd("long process", execx.CmdOptions{ Args: []string{"sleep", "60"}, Executor: execx.NewFakeExecutor(), @@ -162,6 +163,8 @@ func Test_Cmd_Stop(t *testing.T) { } func Test_CmdLog_String(t *testing.T) { + t.Parallel() + cmdLog := &execx.CmdLog{ Out: bytes.NewBufferString("foo"), Err: bytes.NewBufferString("bar"), @@ -233,8 +236,8 @@ func TestCombinedOutput(t *testing.T) { t.Fatal(err) } - if string(ret) != tC.want { - t.Errorf("want = %s, got = %s", tC.want, string(ret)) + if ret != tC.want { + t.Errorf("want = %s, got = %s", tC.want, ret) } }) } From 768e2f3ba066da3e3cedc33ca4fe34d059f5d071 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 2 May 2023 13:44:10 +0200 Subject: [PATCH 293/383] feat: add stop func to git runner --- internal/tool/git/runner.go | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/internal/tool/git/runner.go b/internal/tool/git/runner.go index e81b37dd9..0f0a6ae72 100644 --- a/internal/tool/git/runner.go +++ b/internal/tool/git/runner.go @@ -7,6 +7,8 @@ package git import ( "fmt" + "github.com/google/uuid" + execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -18,6 +20,7 @@ type Paths struct { type Runner struct { executor execx.Executor paths Paths + cmds map[string]*execx.Cmd } func NewRunner(executor execx.Executor, paths Paths) *Runner { @@ -31,15 +34,41 @@ func (r *Runner) CmdPath() string { return r.paths.Git } -func (r *Runner) Version() (string, error) { - out, err := execx.CombinedOutput(execx.NewCmd(r.paths.Git, execx.CmdOptions{ - Args: []string{"version"}, +func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { + cmd := execx.NewCmd(r.paths.Git, execx.CmdOptions{ + Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, - })) + }) + + id := uuid.NewString() + r.cmds[id] = cmd + + return cmd, id +} + +func (r *Runner) deleteCmd(id string) { + delete(r.cmds, id) +} + +func (r *Runner) Version() (string, error) { + cmd, id := r.newCmd([]string{"version"}) + defer r.deleteCmd(id) + + out, err := execx.CombinedOutput(cmd) if err != nil { return "", fmt.Errorf("error getting git version: %w", err) } return out, nil } + +func (r *Runner) Stop() error { + for _, cmd := range r.cmds { + if err := cmd.Stop(); err != nil { + return fmt.Errorf("error stopping git runner: %w", err) + } + } + + return nil +} From 147f0efbd152621b11fd4bf1a9aa31393ff3bec2 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 2 May 2023 17:20:35 +0200 Subject: [PATCH 294/383] fix: fix test --- internal/tool/git/runner.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/tool/git/runner.go b/internal/tool/git/runner.go index 0f0a6ae72..f3d35891d 100644 --- a/internal/tool/git/runner.go +++ b/internal/tool/git/runner.go @@ -27,6 +27,7 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { return &Runner{ executor: executor, paths: paths, + cmds: make(map[string]*execx.Cmd), } } From f17db46679ae09f41175466bee0e9e76d46dd73b Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 8 May 2023 14:15:02 +0200 Subject: [PATCH 295/383] feat: execute stops in parallel --- internal/apis/kfd/v1alpha2/eks/creator.go | 37 +++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index af1459c21..2e737cd28 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -11,6 +11,7 @@ import ( "path" "path/filepath" "strings" + "sync" "time" "github.com/sirupsen/logrus" @@ -190,17 +191,35 @@ func (v *ClusterCreator) Create(skipPhase string, timeout int) error { } case cluster.OperationPhaseAll: - if err := infra.Stop(); err != nil { - return fmt.Errorf("error stopping infrastructure phase: %w", err) - } + var stopWg sync.WaitGroup - if err := kube.Stop(); err != nil { - return fmt.Errorf("error stopping kubernetes phase: %w", err) - } + stopWg.Add(3) - if err := distro.Stop(); err != nil { - return fmt.Errorf("error stopping distribution phase: %w", err) - } + go func() { + if err := infra.Stop(); err != nil { + logrus.Error(err) + } + + stopWg.Done() + }() + + go func() { + if err := kube.Stop(); err != nil { + logrus.Error(err) + } + + stopWg.Done() + }() + + go func() { + if err := distro.Stop(); err != nil { + logrus.Error(err) + } + + stopWg.Done() + }() + + stopWg.Wait() } return ErrTimeout From 14c173885b9e8be6542c9f6018410137944aa2ae Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 8 May 2023 17:07:02 +0200 Subject: [PATCH 296/383] fix linting --- internal/apis/kfd/v1alpha2/eks/creator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 2e737cd28..3a61c13c5 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -193,6 +193,7 @@ func (v *ClusterCreator) Create(skipPhase string, timeout int) error { case cluster.OperationPhaseAll: var stopWg sync.WaitGroup + //nolint:gomnd,revive // ignore magic number linters stopWg.Add(3) go func() { From 1363c6e1535b3b6959c171ea6994bc87e11ac48e Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 8 May 2023 17:46:42 +0200 Subject: [PATCH 297/383] fix: increase furyctl default timeout to 1 hour --- cmd/create/cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 5b716c567..7db4224ad 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -438,7 +438,7 @@ func setupCreateClusterCmdFlags(cmd *cobra.Command) { //nolint:gomnd,revive // ignore magic number linters cmd.Flags().Int( "timeout", - 60, + 3600, "Timeout in seconds for the whole cluster creation process. Expressed in seconds", ) } From 6f57778ef83347029ce86e962dcde37c171d1433 Mon Sep 17 00:00:00 2001 From: Santiago Merlos Date: Tue, 9 May 2023 11:01:08 +0200 Subject: [PATCH 298/383] fix(jsonschema): add missing aws regions --- .../public/ekscluster-kfd-v1alpha2.json | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/test/data/e2e/create/cluster/kubernetes/data/schemas/public/ekscluster-kfd-v1alpha2.json b/test/data/e2e/create/cluster/kubernetes/data/schemas/public/ekscluster-kfd-v1alpha2.json index f1231a976..b3c368a49 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/schemas/public/ekscluster-kfd-v1alpha2.json +++ b/test/data/e2e/create/cluster/kubernetes/data/schemas/public/ekscluster-kfd-v1alpha2.json @@ -1919,30 +1919,36 @@ "Types.AwsRegion": { "type": "string", "enum": [ + "us-east-2", + "us-east-1", + "us-west-1", + "us-west-2", "af-south-1", "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", + "ap-south-2", + "ap-southeast-3", + "ap-southeast-4", "ap-south-1", + "ap-northeast-3", + "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", - "ap-southeast-3", + "ap-northeast-1", "ca-central-1", "eu-central-1", - "eu-north-1", - "eu-south-1", "eu-west-1", "eu-west-2", + "eu-south-1", "eu-west-3", - "me-central-1", + "eu-south-2", + "eu-north-1", + "eu-central-2", "me-south-1", + "me-central-1", "sa-east-1", - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2" - ] + "us-gov-east-1", + "us-gov-west-1" + ] }, "Types.AwsVpcId": { "type": "string", From d9b992dc144d8db6b6feebbef0b0d2217a17b719 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 9 May 2023 10:21:38 +0200 Subject: [PATCH 299/383] feat: upgrade to new distribution validation files --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fe7e890f4..048a4e06c 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.2-0.20230414160033-2b07e27145d8 + github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index 8b66fedee..f72c901b5 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,8 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.2-0.20230414160033-2b07e27145d8 h1:jHEi85IFEA5AQPYnPPPoTnjxfsq3K20c4p4bw5Kwenk= -github.com/sighupio/fury-distribution v1.25.2-0.20230414160033-2b07e27145d8/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa h1:RRqHUXqeoN7hKOTamxPckpn375tI0vT3mUCBnB5AQ9s= +github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 75962f5fecd8214f7ee137e99d296276332db7b3 Mon Sep 17 00:00:00 2001 From: omissis Date: Tue, 9 May 2023 18:19:30 +0200 Subject: [PATCH 300/383] chore: bump fury-distribution dep --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 048a4e06c..4fcd32f7e 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa + github.com/sighupio/fury-distribution v1.25.3-0.20230509161240-41d6d3d46302 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index f72c901b5..4ef894692 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa h1:RRqHUXqeoN7hKOTamxPckpn375tI0vT3mUCBnB5AQ9s= github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= +github.com/sighupio/fury-distribution v1.25.3-0.20230509161240-41d6d3d46302 h1:P2SPHvn+wdCeNDkFfLmQ0WcEmwIPQ5g6kWp28qTVrqE= +github.com/sighupio/fury-distribution v1.25.3-0.20230509161240-41d6d3d46302/go.mod h1:cYHT8oRWuVyrf9515sVEGKCo+WrOSKk1IVRSxA3x1l0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 057cd5a27dd23bc2eb802b452e11249a2b462d71 Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 15 May 2023 11:51:33 +0200 Subject: [PATCH 301/383] update codebase to make it work with terraform 1.x modules - add logging to openvpn connect command - moved the cluster connectivity check later in the distribution phase - rework the terraform output management to avoid extracting it from the logs --- cmd/connect/openvpn.go | 8 +++ .../kfd/v1alpha2/eks/create/distribution.go | 48 +++++++++--------- .../kfd/v1alpha2/eks/create/kubernetes.go | 16 +++--- internal/tool/terraform/runner.go | 49 +++++++------------ 4 files changed, 59 insertions(+), 62 deletions(-) diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go index 33447d66b..307cb12ba 100644 --- a/cmd/connect/openvpn.go +++ b/cmd/connect/openvpn.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/sighupio/fury-distribution/pkg/config" @@ -42,7 +43,10 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd)) }, RunE: func(cmd *cobra.Command, _ []string) error { + logrus.Info("Connecting to OpenVPN...") + // Parse flags. + logrus.Debug("Parsing VPN Flags...") flags, err := getOpenVPNCmdFlags(cmd, tracker, cmdEvent) if err != nil { return err @@ -53,6 +57,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { } // Get home dir. + logrus.Debug("Getting Home Directory Path...") homeDir, err := os.UserHomeDir() if err != nil { cmdEvent.AddErrorMessage(err) @@ -62,6 +67,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { } // Parse furyctl.yaml config. + logrus.Debug("Parsing furyctl.yaml file...") furyctlConf, err := yamlx.FromFileV3[config.Furyctl](flags.FuryctlPath) if err != nil { cmdEvent.AddErrorMessage(err) @@ -71,6 +77,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { } // Set common paths. + logrus.Debug("Setting common paths...") basePath := filepath.Join(homeDir, ".furyctl", furyctlConf.Metadata.Name) openVPNWorkDir := filepath.Join(basePath, "infrastructure", "terraform", "secrets") @@ -82,6 +89,7 @@ func NewOpenVPNCmd(tracker *analytics.Tracker) *cobra.Command { }) // Start openvpn process. + logrus.Debug("Running OpenVPN...") if err := openVPNCmd.Run(); err != nil { err = fmt.Errorf("%w: %w", ErrRunningOpenVPN, err) cmdEvent.AddErrorMessage(err) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 6786b5567..c59f87dad 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -126,21 +126,6 @@ func (d *Distribution) Exec() error { logrus.Debug("Create: running distribution phase...") - logrus.Info("Checking that the cluster is reachable...") - - if _, err := d.kubeRunner.Version(); err != nil { - logrus.Debugf("Got error while running cluster reachability check: %s", err) - - if !d.dryRun { - return errClusterConnect - } - - if d.phase == cluster.OperationPhaseDistribution { - logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + - "running the command without --dry-run") - } - } - if err := d.CreateFolder(); err != nil { return fmt.Errorf("error creating distribution phase folder: %w", err) } @@ -231,6 +216,21 @@ func (d *Distribution) Exec() error { return err } + logrus.Info("Checking that the cluster is reachable...") + + if _, err := d.kubeRunner.Version(); err != nil { + logrus.Debugf("Got error while running cluster reachability check: %s", err) + + if !d.dryRun { + return errClusterConnect + } + + if d.phase == cluster.OperationPhaseDistribution { + logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + + "running the command without --dry-run") + } + } + logrus.Info("Applying manifests...") return d.applyManifests(manifestsOutPath) @@ -376,11 +376,11 @@ func (d *Distribution) extractVpcIDFromPrevPhases(fMerger *merge.Merger) (string var infraOut terraform.OutputJSON if err := json.Unmarshal(infraOutJSON, &infraOut); err == nil { - if infraOut.Outputs["vpc_id"] == nil { + if infraOut["vpc_id"] == nil { return vpcID, ErrVpcIDNotFound } - vpcIDOut, ok := infraOut.Outputs["vpc_id"].Value.(string) + vpcIDOut, ok := infraOut["vpc_id"].Value.(string) if !ok { return vpcID, errCastingVpcIDToStr } @@ -512,7 +512,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { return nil, fmt.Errorf("error unmarshaling distribution output: %w", err) } - ebsCsiDriverArn, ok := distroOut.Outputs["ebs_csi_driver_iam_role_arn"] + ebsCsiDriverArn, ok := distroOut["ebs_csi_driver_iam_role_arn"] if ok { arns["ebs_csi_driver_iam_role_arn"], ok = ebsCsiDriverArn.Value.(string) if !ok { @@ -520,7 +520,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - loadBalancerControllerArn, ok := distroOut.Outputs["load_balancer_controller_iam_role_arn"] + loadBalancerControllerArn, ok := distroOut["load_balancer_controller_iam_role_arn"] if ok { arns["load_balancer_controller_iam_role_arn"], ok = loadBalancerControllerArn.Value.(string) if !ok { @@ -528,7 +528,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - clusterAutoscalerArn, ok := distroOut.Outputs["cluster_autoscaler_iam_role_arn"] + clusterAutoscalerArn, ok := distroOut["cluster_autoscaler_iam_role_arn"] if ok { arns["cluster_autoscaler_iam_role_arn"], ok = clusterAutoscalerArn.Value.(string) if !ok { @@ -536,7 +536,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - externalDNSPrivateArn, ok := distroOut.Outputs["external_dns_private_iam_role_arn"] + externalDNSPrivateArn, ok := distroOut["external_dns_private_iam_role_arn"] if ok { arns["external_dns_private_iam_role_arn"], ok = externalDNSPrivateArn.Value.(string) if !ok { @@ -544,7 +544,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - externalDNSPublicArn, ok := distroOut.Outputs["external_dns_public_iam_role_arn"] + externalDNSPublicArn, ok := distroOut["external_dns_public_iam_role_arn"] if ok { arns["external_dns_public_iam_role_arn"], ok = externalDNSPublicArn.Value.(string) if !ok { @@ -552,7 +552,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - certManagerArn, ok := distroOut.Outputs["cert_manager_iam_role_arn"] + certManagerArn, ok := distroOut["cert_manager_iam_role_arn"] if ok { arns["cert_manager_iam_role_arn"], ok = certManagerArn.Value.(string) if !ok { @@ -560,7 +560,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - veleroArn, ok := distroOut.Outputs["velero_iam_role_arn"] + veleroArn, ok := distroOut["velero_iam_role_arn"] if ok { arns["velero_iam_role_arn"], ok = veleroArn.Value.(string) if !ok { diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 71b6c46c8..e7290d795 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -206,11 +206,11 @@ func (k *Kubernetes) Exec() error { return fmt.Errorf("cannot create cloud resources: %w", err) } - if out.Outputs["kubeconfig"] == nil { + if out["kubeconfig"] == nil { return errKubeconfigFromLogs } - kubeString, ok := out.Outputs["kubeconfig"].Value.(string) + kubeString, ok := out["kubeconfig"].Value.(string) if !ok { return errKubeconfigFromLogs } @@ -436,29 +436,29 @@ func (k *Kubernetes) createTfVars() error { var infraOut terraform.OutputJSON if err := json.Unmarshal(infraOutJSON, &infraOut); err == nil { - if infraOut.Outputs["private_subnets"] == nil { + if infraOut["private_subnets"] == nil { return errPvtSubnetNotFound } - s, ok := infraOut.Outputs["private_subnets"].Value.([]any) + s, ok := infraOut["private_subnets"].Value.([]any) if !ok { return errPvtSubnetFromOut } - if infraOut.Outputs["vpc_id"] == nil { + if infraOut["vpc_id"] == nil { return ErrVpcIDNotFound } - v, ok := infraOut.Outputs["vpc_id"].Value.(string) + v, ok := infraOut["vpc_id"].Value.(string) if !ok { return ErrVpcIDFromOut } - if infraOut.Outputs["vpc_cidr_block"] == nil { + if infraOut["vpc_cidr_block"] == nil { return errVpcCIDRNotFound } - c, ok := infraOut.Outputs["vpc_cidr_block"].Value.(string) + c, ok := infraOut["vpc_cidr_block"].Value.(string) if !ok { return errVpcCIDRFromOut } diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index 196ed10e5..c2b69ef5e 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -10,7 +10,6 @@ import ( "fmt" "os" "path" - "regexp" "github.com/google/uuid" tfjson "github.com/hashicorp/terraform-json" @@ -21,9 +20,7 @@ import ( var errOutputFromApply = errors.New("can't get outputs from terraform apply logs") -type OutputJSON struct { - Outputs map[string]*tfjson.StateOutput `json:"outputs"` -} +type OutputJSON map[string]*tfjson.StateOutput type Paths struct { Logs string @@ -118,46 +115,38 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { var oj OutputJSON - args := []string{"apply", "-no-color", "-json", "plan/terraform.plan"} - - cmd, id := r.newCmd(args) - defer r.deleteCmd(id) + cmd, applyId := r.newCmd([]string{"apply", "-no-color", "-json", "plan/terraform.plan"}) + defer r.deleteCmd(applyId) if err := cmd.Run(); err != nil { return oj, fmt.Errorf("cannot create cloud resources: %w", err) } - err := os.WriteFile(path.Join(r.paths.Logs, - fmt.Sprintf("%d.log", timestamp)), + if err := os.WriteFile( + path.Join(r.paths.Logs, fmt.Sprintf("%d.log", timestamp)), cmd.Log.Out.Bytes(), - iox.FullRWPermAccess) - if err != nil { + iox.FullRWPermAccess, + ); err != nil { return oj, fmt.Errorf("error writing terraform apply log: %w", err) } - parsedApplyLog, err := os.ReadFile(path.Join(r.paths.Logs, fmt.Sprintf("%d.log", timestamp))) - if err != nil { - return oj, fmt.Errorf("error reading terraform apply log: %w", err) - } - - applyLog := string(parsedApplyLog) + cmd, outputId := r.newCmd([]string{"output", "-json"}) + defer r.deleteCmd(outputId) - pattern := regexp.MustCompile("(\"outputs\":){(.*?)}}") - - outputsStringIndex := pattern.FindStringIndex(applyLog) - if outputsStringIndex == nil { - return oj, errOutputFromApply + if err := cmd.Run(); err != nil { + return oj, fmt.Errorf("cannot access terraform apply output: %w", err) } - outputsString := fmt.Sprintf("{%s}", applyLog[outputsStringIndex[0]:outputsStringIndex[1]]) - - if err := json.Unmarshal([]byte(outputsString), &oj); err != nil { - return oj, fmt.Errorf("error unmarshalling terraform apply outputs: %w", err) + if err := os.WriteFile( + path.Join(r.paths.Outputs, "output.json"), + cmd.Log.Out.Bytes(), + iox.FullRWPermAccess, + ); err != nil { + return oj, fmt.Errorf("error writing terraform apply outputs: %w", err) } - err = os.WriteFile(path.Join(r.paths.Outputs, "output.json"), []byte(outputsString), iox.FullRWPermAccess) - if err != nil { - return oj, fmt.Errorf("error writing terraform apply outputs: %w", err) + if err := json.Unmarshal(cmd.Log.Out.Bytes(), &oj); err != nil { + return oj, fmt.Errorf("error unmarshalling terraform apply outputs: %w", err) } return oj, nil From a9be563798910ff1f13a6b879724acb6901fed81 Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 15 May 2023 12:29:43 +0200 Subject: [PATCH 302/383] fix: add output fake command for tf runner tests --- internal/tool/terraform/runner_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/tool/terraform/runner_test.go b/internal/tool/terraform/runner_test.go index eb37a7145..3e42b0d6c 100644 --- a/internal/tool/terraform/runner_test.go +++ b/internal/tool/terraform/runner_test.go @@ -136,6 +136,8 @@ func TestHelperProcess(t *testing.T) { fmt.Fprintf(os.Stdout, `{"outputs":{"foo":{"sensitive":false,"value":"bar"}}}`) case "version": fmt.Fprintf(os.Stdout, "v1.2.3") + case "output": + fmt.Fprintf(os.Stdout, `{"outputs":{"foo":{"sensitive":false,"value":"bar"}}}`) default: fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) } From bc84cf953cfa0075fcd0e9d2a9a7a8c45e61bb5d Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 15 May 2023 14:07:18 +0200 Subject: [PATCH 303/383] fix: code linting --- internal/tool/terraform/runner.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index c2b69ef5e..6ef5f5bb7 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -6,7 +6,6 @@ package terraform import ( "encoding/json" - "errors" "fmt" "os" "path" @@ -18,8 +17,6 @@ import ( iox "github.com/sighupio/furyctl/internal/x/io" ) -var errOutputFromApply = errors.New("can't get outputs from terraform apply logs") - type OutputJSON map[string]*tfjson.StateOutput type Paths struct { @@ -115,8 +112,8 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { var oj OutputJSON - cmd, applyId := r.newCmd([]string{"apply", "-no-color", "-json", "plan/terraform.plan"}) - defer r.deleteCmd(applyId) + cmd, applyID := r.newCmd([]string{"apply", "-no-color", "-json", "plan/terraform.plan"}) + defer r.deleteCmd(applyID) if err := cmd.Run(); err != nil { return oj, fmt.Errorf("cannot create cloud resources: %w", err) @@ -130,8 +127,8 @@ func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { return oj, fmt.Errorf("error writing terraform apply log: %w", err) } - cmd, outputId := r.newCmd([]string{"output", "-json"}) - defer r.deleteCmd(outputId) + cmd, outputID := r.newCmd([]string{"output", "-json"}) + defer r.deleteCmd(outputID) if err := cmd.Run(); err != nil { return oj, fmt.Errorf("cannot access terraform apply output: %w", err) From 1ec207e1da182f16890d541ff744a5210ab775fc Mon Sep 17 00:00:00 2001 From: omissis Date: Mon, 15 May 2023 17:54:36 +0200 Subject: [PATCH 304/383] chore: refactor tf output command out of tf apply --- .../kfd/v1alpha2/eks/create/distribution.go | 3 +-- .../kfd/v1alpha2/eks/create/infrastructure.go | 2 +- .../apis/kfd/v1alpha2/eks/create/kubernetes.go | 8 ++++++-- internal/tool/terraform/runner.go | 14 +++++++++----- internal/tool/terraform/runner_test.go | 18 +++++++++++++++++- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index c59f87dad..60797071d 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -190,8 +190,7 @@ func (d *Distribution) Exec() error { logrus.Warn("Creating cloud resources, this could take a while...") - _, err = d.tfRunner.Apply(timestamp) - if err != nil { + if err := d.tfRunner.Apply(timestamp); err != nil { return fmt.Errorf("cannot create cloud resources: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 0a3f3ed34..538681545 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -144,7 +144,7 @@ func (i *Infrastructure) Exec() error { logrus.Warn("Creating cloud resources, this could take a while...") - if _, err := i.tfRunner.Apply(timestamp); err != nil { + if err := i.tfRunner.Apply(timestamp); err != nil { return fmt.Errorf("cannot create cloud resources: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index e7290d795..c0aff961f 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -201,11 +201,15 @@ func (k *Kubernetes) Exec() error { logrus.Warn("Creating cloud resources, this could take a while...") - out, err := k.tfRunner.Apply(timestamp) - if err != nil { + if err := k.tfRunner.Apply(timestamp); err != nil { return fmt.Errorf("cannot create cloud resources: %w", err) } + out, err := k.tfRunner.Output() + if err != nil { + return fmt.Errorf("error getting terraform output: %w", err) + } + if out["kubeconfig"] == nil { return errKubeconfigFromLogs } diff --git a/internal/tool/terraform/runner.go b/internal/tool/terraform/runner.go index 6ef5f5bb7..5b2069e81 100644 --- a/internal/tool/terraform/runner.go +++ b/internal/tool/terraform/runner.go @@ -109,14 +109,12 @@ func (r *Runner) Plan(timestamp int64, params ...string) ([]byte, error) { return out, nil } -func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { - var oj OutputJSON - +func (r *Runner) Apply(timestamp int64) error { cmd, applyID := r.newCmd([]string{"apply", "-no-color", "-json", "plan/terraform.plan"}) defer r.deleteCmd(applyID) if err := cmd.Run(); err != nil { - return oj, fmt.Errorf("cannot create cloud resources: %w", err) + return fmt.Errorf("cannot create cloud resources: %w", err) } if err := os.WriteFile( @@ -124,9 +122,15 @@ func (r *Runner) Apply(timestamp int64) (OutputJSON, error) { cmd.Log.Out.Bytes(), iox.FullRWPermAccess, ); err != nil { - return oj, fmt.Errorf("error writing terraform apply log: %w", err) + return fmt.Errorf("error writing terraform apply log: %w", err) } + return nil +} + +func (r *Runner) Output() (OutputJSON, error) { + var oj OutputJSON + cmd, outputID := r.newCmd([]string{"output", "-json"}) defer r.deleteCmd(outputID) diff --git a/internal/tool/terraform/runner_test.go b/internal/tool/terraform/runner_test.go index 3e42b0d6c..0b1c954ac 100644 --- a/internal/tool/terraform/runner_test.go +++ b/internal/tool/terraform/runner_test.go @@ -64,7 +64,7 @@ func Test_Runner_Apply(t *testing.T) { r := terraform.NewRunner(execx.NewFakeExecutor(), paths) - if _, err := r.Apply(42); err != nil { + if err := r.Apply(42); err != nil { t.Fatal(err) } @@ -76,6 +76,22 @@ func Test_Runner_Apply(t *testing.T) { if info1.Size() == 0 { t.Error("expected '42.log' file to be not empty") } +} + +func Test_Runner_Output(t *testing.T) { + paths := terraform.Paths{ + Terraform: "terraform", + WorkDir: test.MkdirTemp(t), + Logs: test.MkdirTemp(t), + Outputs: test.MkdirTemp(t), + Plan: test.MkdirTemp(t), + } + + r := terraform.NewRunner(execx.NewFakeExecutor(), paths) + + if _, err := r.Output(); err != nil { + t.Fatal(err) + } info2, err := os.Stat(filepath.Join(paths.Outputs, "output.json")) if err != nil { From 6b9cf890f3ed782f22bea93912f2a16ee4fa816c Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 7 Feb 2023 12:29:38 +0100 Subject: [PATCH 305/383] feat: switch fixed amd64 value to GOARCH --- internal/dependencies/tools/ansible.go | 2 +- internal/dependencies/tools/furyagent.go | 2 +- internal/dependencies/tools/kubectl.go | 2 +- internal/dependencies/tools/kustomize.go | 2 +- internal/dependencies/tools/openvpn.go | 2 +- internal/dependencies/tools/terraform.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/dependencies/tools/ansible.go b/internal/dependencies/tools/ansible.go index 0d94657db..037f4eb96 100644 --- a/internal/dependencies/tools/ansible.go +++ b/internal/dependencies/tools/ansible.go @@ -15,7 +15,7 @@ import ( func NewAnsible(runner *ansible.Runner, version string) *Ansible { return &Ansible{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ diff --git a/internal/dependencies/tools/furyagent.go b/internal/dependencies/tools/furyagent.go index 63aa4e511..f449828fd 100644 --- a/internal/dependencies/tools/furyagent.go +++ b/internal/dependencies/tools/furyagent.go @@ -18,7 +18,7 @@ import ( func NewFuryagent(runner *furyagent.Runner, version string) *Furyagent { return &Furyagent{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ diff --git a/internal/dependencies/tools/kubectl.go b/internal/dependencies/tools/kubectl.go index e28d8ecc8..914e4a164 100644 --- a/internal/dependencies/tools/kubectl.go +++ b/internal/dependencies/tools/kubectl.go @@ -16,7 +16,7 @@ import ( func NewKubectl(runner *kubectl.Runner, version string) *Kubectl { return &Kubectl{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ diff --git a/internal/dependencies/tools/kustomize.go b/internal/dependencies/tools/kustomize.go index 6acdf3b2d..e11caa96e 100644 --- a/internal/dependencies/tools/kustomize.go +++ b/internal/dependencies/tools/kustomize.go @@ -17,7 +17,7 @@ import ( func NewKustomize(runner *kustomize.Runner, version string) *Kustomize { return &Kustomize{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ diff --git a/internal/dependencies/tools/openvpn.go b/internal/dependencies/tools/openvpn.go index 95670adcc..43bcfa536 100644 --- a/internal/dependencies/tools/openvpn.go +++ b/internal/dependencies/tools/openvpn.go @@ -16,7 +16,7 @@ import ( func NewOpenvpn(runner *openvpn.Runner, version string) *Openvpn { return &Openvpn{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ diff --git a/internal/dependencies/tools/terraform.go b/internal/dependencies/tools/terraform.go index bc3fe19b2..332cf475d 100644 --- a/internal/dependencies/tools/terraform.go +++ b/internal/dependencies/tools/terraform.go @@ -17,7 +17,7 @@ import ( func NewTerraform(runner *terraform.Runner, version string) *Terraform { return &Terraform{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ From fcbabaa964a3d750da7f2d4cadc670176402ab3b Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 7 Feb 2023 12:31:23 +0100 Subject: [PATCH 306/383] fix: linting --- internal/dependencies/tools/awscli.go | 1 - internal/dependencies/tools/openvpn.go | 1 - 2 files changed, 2 deletions(-) diff --git a/internal/dependencies/tools/awscli.go b/internal/dependencies/tools/awscli.go index 38dc97e62..5af6163ec 100644 --- a/internal/dependencies/tools/awscli.go +++ b/internal/dependencies/tools/awscli.go @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//nolint:dupl // false positive package tools import ( diff --git a/internal/dependencies/tools/openvpn.go b/internal/dependencies/tools/openvpn.go index 43bcfa536..73df42ed4 100644 --- a/internal/dependencies/tools/openvpn.go +++ b/internal/dependencies/tools/openvpn.go @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//nolint:dupl // false positive package tools import ( From d286b799ad0f8d006aaafb382d213176c3df03bf Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 19 May 2023 10:57:49 +0200 Subject: [PATCH 307/383] chore: reverted kustomize to amd64 --- internal/dependencies/tools/kustomize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/dependencies/tools/kustomize.go b/internal/dependencies/tools/kustomize.go index e11caa96e..6acdf3b2d 100644 --- a/internal/dependencies/tools/kustomize.go +++ b/internal/dependencies/tools/kustomize.go @@ -17,7 +17,7 @@ import ( func NewKustomize(runner *kustomize.Runner, version string) *Kustomize { return &Kustomize{ - arch: runtime.GOARCH, + arch: "amd64", os: runtime.GOOS, version: version, checker: &checker{ From 1e2b25d626cb814a0dc936cd806239e730bfa778 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 19 May 2023 11:01:57 +0200 Subject: [PATCH 308/383] fix: linting --- internal/dependencies/tools/kustomize.go | 1 - internal/dependencies/tools/terraform.go | 1 - 2 files changed, 2 deletions(-) diff --git a/internal/dependencies/tools/kustomize.go b/internal/dependencies/tools/kustomize.go index 6acdf3b2d..4a644bff9 100644 --- a/internal/dependencies/tools/kustomize.go +++ b/internal/dependencies/tools/kustomize.go @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//nolint:dupl // false positive package tools import ( diff --git a/internal/dependencies/tools/terraform.go b/internal/dependencies/tools/terraform.go index 332cf475d..9cc0a56d6 100644 --- a/internal/dependencies/tools/terraform.go +++ b/internal/dependencies/tools/terraform.go @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//nolint:dupl // false positive package tools import ( From 7a29bc80f8a9b3a865c0835a2c46e36046ea8bf0 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Wed, 24 May 2023 15:10:58 +0200 Subject: [PATCH 309/383] fix: missing output gen in infrastructure phase --- internal/apis/kfd/v1alpha2/eks/create/infrastructure.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 538681545..6fe39f402 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -148,6 +148,10 @@ func (i *Infrastructure) Exec() error { return fmt.Errorf("cannot create cloud resources: %w", err) } + if _, err := i.tfRunner.Output(); err != nil { + return fmt.Errorf("error getting terraform output: %w", err) + } + return nil } From b8537df29701fb108ff60d0c20828dbec874aabe Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 6 Jun 2023 12:10:28 +0200 Subject: [PATCH 310/383] fix: make os in release artifacts names lowercase --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index d4e40ced6..3d7182e82 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -22,7 +22,7 @@ builds: archives: - name_template: >- {{ .ProjectName }}_ - {{- title .Os }}_ + {{- tolower .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} From 0a559f3dafa36411ec4553f257828ca427a50a8a Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 6 Jun 2023 12:24:12 +0200 Subject: [PATCH 311/383] fix: make release artifact names compatible with the older fury format --- .goreleaser.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 3d7182e82..ebb090a15 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -20,12 +20,7 @@ builds: ldflags: - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} archives: - - name_template: >- - {{ .ProjectName }}_ - {{- tolower .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} + - name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}" checksum: name_template: 'checksums.txt' snapshot: From 987fe323a7ce7c39981ebe4025fd0682ceb41855 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Wed, 14 Jun 2023 16:53:54 +0200 Subject: [PATCH 312/383] feat: add support for cluster_service_ipv4_cidr variable --- configs/provisioners/bootstrap/aws/main.tf.tpl | 8 ++++++++ configs/provisioners/cluster/eks/main.tf.tpl | 6 +++++- configs/provisioners/cluster/eks/variables.tf | 6 ++++++ go.sum | 10 ++++++---- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 9 +++++++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index 06c3526e5..f36e1343d 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -40,6 +40,12 @@ module "vpc" { cidr = var.cidr private_subnetwork_cidrs = var.vpc_private_subnetwork_cidrs public_subnetwork_cidrs = var.vpc_public_subnetwork_cidrs + + # extra_ipv4_cidr_blocks = [] + # availability_zone_names = [] + # single_nat_gateway = false + # one_nat_gateway_per_az = true + # names_of_kubernetes_cluster_integrated_with_subnets = [] } module "vpn" { @@ -62,4 +68,6 @@ module "vpn" { vpn_dhparams_bits = var.vpn_dhparams_bits vpn_operator_cidrs = var.vpn_operator_cidrs vpn_ssh_users = var.vpn_ssh_users + + # vpn_routes = [] } diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index b8b8605e0..673652397 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -55,6 +55,7 @@ module "fury" { cluster_endpoint_public_access_cidrs = var.cluster_endpoint_public_access_cidrs cluster_endpoint_private_access = var.cluster_endpoint_private_access cluster_endpoint_private_access_cidrs = var.cluster_endpoint_private_access_cidrs + cluster_service_ipv4_cidr = var.cluster_service_ipv4_cidr vpc_id = var.vpc_id subnets = var.subnets ssh_public_key = var.ssh_public_key @@ -62,9 +63,12 @@ module "fury" { node_pools_launch_kind = var.node_pools_launch_kind tags = var.tags - # Specific AWS variables. + # AWS-specific variables. # Enables managing auth using these variables eks_map_users = var.eks_map_users eks_map_roles = var.eks_map_roles eks_map_accounts = var.eks_map_accounts + + # availability_zone_names = [] + # ssh_to_nodes_allowed_cidr_blocks = [] } diff --git a/configs/provisioners/cluster/eks/variables.tf b/configs/provisioners/cluster/eks/variables.tf index 4ab9c7480..72146fc73 100644 --- a/configs/provisioners/cluster/eks/variables.tf +++ b/configs/provisioners/cluster/eks/variables.tf @@ -39,6 +39,12 @@ variable "cluster_endpoint_public_access_cidrs" { default = ["0.0.0.0/0"] } +variable "cluster_service_ipv4_cidr" { + type = string + description = "The CIDR block to assign Kubernetes service IP addresses from" + default = null +} + variable "vpc_id" { type = string description = "VPC ID where the Kubernetes cluster will be hosted" diff --git a/go.sum b/go.sum index 4ef894692..98f61be6e 100644 --- a/go.sum +++ b/go.sum @@ -298,10 +298,12 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa h1:RRqHUXqeoN7hKOTamxPckpn375tI0vT3mUCBnB5AQ9s= -github.com/sighupio/fury-distribution v1.25.3-0.20230508164630-9cdab16757aa/go.mod h1:YoK8hAXlril/Ow4WXgDQVjUI2r1SGzvIluR8rvVI/r4= -github.com/sighupio/fury-distribution v1.25.3-0.20230509161240-41d6d3d46302 h1:P2SPHvn+wdCeNDkFfLmQ0WcEmwIPQ5g6kWp28qTVrqE= -github.com/sighupio/fury-distribution v1.25.3-0.20230509161240-41d6d3d46302/go.mod h1:cYHT8oRWuVyrf9515sVEGKCo+WrOSKk1IVRSxA3x1l0= +github.com/sighupio/fury-distribution v1.25.3-0.20230606141031-475b58d95663 h1:qHCISTDMgaeD+I5e9BXw/3LdBYXTbhZc3VXiFBj638E= +github.com/sighupio/fury-distribution v1.25.3-0.20230606141031-475b58d95663/go.mod h1:r8n2QNkNe58Cjsp/qy2jrbc9GIV16zWYZzYiv5UATZc= +github.com/sighupio/fury-distribution v1.25.3-0.20230606151107-27574128fbe6 h1:4s1AGw+w9NlBXugrnZ+/A3u1h/R9J5giO3zt7pnHeso= +github.com/sighupio/fury-distribution v1.25.3-0.20230606151107-27574128fbe6/go.mod h1:r8n2QNkNe58Cjsp/qy2jrbc9GIV16zWYZzYiv5UATZc= +github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28 h1:+UeK6N3lRfCrOOQLKUKt2Wtnjbb2Rn4TyUt365pBfTg= +github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index c0aff961f..20edf1205 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -565,6 +565,15 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_service_ipv4_cidr = \"%v\"\n", + k.furyctlConf.Spec.Kubernetes.ServiceIpV4Cidr, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + err = bytesx.SafeWriteToBuffer( &buffer, "node_pools_launch_kind = \"%v\"\n", From a6d03af9c4fe926675ed961bfa34b6c8d35db0f9 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 12:10:05 +0200 Subject: [PATCH 313/383] fix: handle null value in template dumping for service_ipv4_cidr variable --- .../apis/kfd/v1alpha2/eks/create/kubernetes.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 20edf1205..87ddf01a7 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -565,11 +565,18 @@ func (k *Kubernetes) createTfVars() error { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } - err = bytesx.SafeWriteToBuffer( - &buffer, - "cluster_service_ipv4_cidr = \"%v\"\n", - k.furyctlConf.Spec.Kubernetes.ServiceIpV4Cidr, - ) + if k.furyctlConf.Spec.Kubernetes.ServiceIpV4Cidr == nil { + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_service_ipv4_cidr = null\n", + ) + } else { + err = bytesx.SafeWriteToBuffer( + &buffer, + "cluster_service_ipv4_cidr = \"%v\"\n", + k.furyctlConf.Spec.Kubernetes.ServiceIpV4Cidr, + ) + } if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } From 9ecfdeff25a8c511c708113c10f539241aaf4316 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 14:53:54 +0200 Subject: [PATCH 314/383] fix: linting issue --- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 87ddf01a7..b4b2ed182 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -577,6 +577,7 @@ func (k *Kubernetes) createTfVars() error { k.furyctlConf.Spec.Kubernetes.ServiceIpV4Cidr, ) } + if err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } From 309aea48d5d6d43620b40743cad3b04aaea24967 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 17:27:13 +0200 Subject: [PATCH 315/383] chore: update go deps --- go.mod | 5 ++--- go.sum | 10 ++-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 4fcd32f7e..4a9f7c0fc 100644 --- a/go.mod +++ b/go.mod @@ -18,12 +18,12 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3-0.20230509161240-41d6d3d46302 + github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.2 - golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -57,7 +57,6 @@ require ( github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jsonmaur/aws-regions/v2 v2.3.1 // indirect github.com/klauspost/compress v1.15.13 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect diff --git a/go.sum b/go.sum index 98f61be6e..f363d7cd9 100644 --- a/go.sum +++ b/go.sum @@ -217,8 +217,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jsonmaur/aws-regions/v2 v2.3.1 h1:WWt452LyhjI4ZCRKBSULVHqIGE8/9UqVQOSAzuc2woE= -github.com/jsonmaur/aws-regions/v2 v2.3.1/go.mod h1:NqtmZ2wG5HkrTYFQ+II3BDysj0yek59yjtZjAaCn8lE= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -298,10 +296,6 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.3-0.20230606141031-475b58d95663 h1:qHCISTDMgaeD+I5e9BXw/3LdBYXTbhZc3VXiFBj638E= -github.com/sighupio/fury-distribution v1.25.3-0.20230606141031-475b58d95663/go.mod h1:r8n2QNkNe58Cjsp/qy2jrbc9GIV16zWYZzYiv5UATZc= -github.com/sighupio/fury-distribution v1.25.3-0.20230606151107-27574128fbe6 h1:4s1AGw+w9NlBXugrnZ+/A3u1h/R9J5giO3zt7pnHeso= -github.com/sighupio/fury-distribution v1.25.3-0.20230606151107-27574128fbe6/go.mod h1:r8n2QNkNe58Cjsp/qy2jrbc9GIV16zWYZzYiv5UATZc= github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28 h1:+UeK6N3lRfCrOOQLKUKt2Wtnjbb2Rn4TyUt365pBfTg= github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= @@ -388,8 +382,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w= -golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= From 99698cb48de20204965d82908c9afa4ee92c7bcd Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 09:53:17 +0200 Subject: [PATCH 316/383] chore: improve error messages in deps download --- .../kfd/v1alpha2/eks/create/kubernetes.go | 17 ++++++++-------- internal/dependencies/download.go | 20 +++++++++++++------ internal/x/net/hashicorp.go | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index b4b2ed182..00d43c74b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -41,12 +41,13 @@ import ( ) var ( - errKubeconfigFromLogs = errors.New("cannot get kubeconfig file after cluster creation") - errPvtSubnetNotFound = errors.New("private_subnets not found in infrastructure phase's output") - errPvtSubnetFromOut = errors.New("cannot read private_subnets from infrastructure's output.json") - errVpcCIDRFromOut = errors.New("cannot read vpc_cidr_block from infrastructure's output.json") - errVpcCIDRNotFound = errors.New("vpc_cidr_block not found in infra output") - errVpcIDNotFound = errors.New("vpcId not found: you forgot to specify one in the configuration file " + + errMissingKubeconfig = errors.New("kubeconfig not found in infrastructure phase's logs") + errWrongKubeconfig = errors.New("kubeconfig cannot be parsed from infrastructure phase's logs") + errPvtSubnetNotFound = errors.New("private_subnets not found in infrastructure phase's output") + errPvtSubnetFromOut = errors.New("cannot read private_subnets from infrastructure's output.json") + errVpcCIDRFromOut = errors.New("cannot read vpc_cidr_block from infrastructure's output.json") + errVpcCIDRNotFound = errors.New("vpc_cidr_block not found in infra output") + errVpcIDNotFound = errors.New("vpcId not found: you forgot to specify one in the configuration file " + "or the infrastructure phase failed") errParsingCIDR = errors.New("error parsing CIDR") errResolvingDNS = errors.New("error resolving DNS record") @@ -211,12 +212,12 @@ func (k *Kubernetes) Exec() error { } if out["kubeconfig"] == nil { - return errKubeconfigFromLogs + return errMissingKubeconfig } kubeString, ok := out["kubeconfig"].Value.(string) if !ok { - return errKubeconfigFromLogs + return errWrongKubeconfig } p, err := kubex.CreateConfig([]byte(kubeString), k.SecretsPath) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 9d6dc6e74..1378c5f63 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -121,18 +121,19 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules, gitPrefix strin for _, prefix := range []string{oldPrefix, newPrefix} { src := fmt.Sprintf("git::%s/%s-%s?ref=%s&depth=1", gitPrefix, prefix, name, version) - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, createURL(prefix, name, version), nil) + moduleURL := createURL(prefix, name, version) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, moduleURL, nil) if err != nil { - return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, err) + return fmt.Errorf("%w '%s' (url: %s): %v", ErrDownloadingModule, name, moduleURL, err) } resp, err := http.DefaultClient.Do(req) if err != nil { if err := resp.Body.Close(); err != nil { - return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, err) + return fmt.Errorf("%w '%s' (url: %s): %v", ErrDownloadingModule, name, moduleURL, err) } - return fmt.Errorf("%w '%s': %v", ErrDownloadingModule, name, err) + return fmt.Errorf("%w '%s' (url: %s): %v", ErrDownloadingModule, name, moduleURL, err) } retries[name]++ @@ -142,8 +143,15 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules, gitPrefix strin if resp.StatusCode != http.StatusOK { if retries[name] >= threshold { - errs = append(errs, fmt.Errorf("%w '%s': please check if module exists or credentials are correctly configured", - ErrModuleNotFound, name)) + errs = append( + errs, + fmt.Errorf( + "%w '%s (url: %s)': please check if module exists or credentials are correctly configured", + ErrModuleNotFound, + name, + moduleURL, + ), + ) } continue diff --git a/internal/x/net/hashicorp.go b/internal/x/net/hashicorp.go index 8405827bd..609faae8f 100644 --- a/internal/x/net/hashicorp.go +++ b/internal/x/net/hashicorp.go @@ -34,7 +34,7 @@ func (g *GoGetterClient) Download(src, dst string) error { for _, protocol := range protocols { fullSrc := fmt.Sprintf("%s%s", protocol, src) - logrus.Debugf("Trying to download from: %s", fullSrc) + logrus.Debugf("Downloading from: %s", fullSrc) client := &getter.Client{ Src: fullSrc, From 1641fc326c19da915bb8597a3368c024d1dd39c9 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 14:24:27 +0200 Subject: [PATCH 317/383] fix: disable terraform state buckets aws region check --- configs/provisioners/bootstrap/aws/main.tf.tpl | 2 ++ configs/provisioners/cluster/eks/main.tf.tpl | 2 ++ 2 files changed, 4 insertions(+) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index f36e1343d..544c87bd1 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -9,6 +9,8 @@ terraform { bucket = "{{ .terraform.backend.s3.bucketName }}" key = "{{ .terraform.backend.s3.keyPrefix }}/infrastructure.json" region = "{{ .terraform.backend.s3.region }}" + + skip_region_validation = true } required_providers { diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index 673652397..1f1b8934e 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -15,6 +15,8 @@ terraform { bucket = "{{ .terraform.backend.s3.bucketName }}" key = "{{ .terraform.backend.s3.keyPrefix }}/cluster.json" region = "{{ .terraform.backend.s3.region }}" + + skip_region_validation = true } required_providers { From a8f278ade75756511309fabca2c4ffa2e955e098 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 17:08:12 +0200 Subject: [PATCH 318/383] fix: linting issue --- internal/dependencies/download.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 1378c5f63..c652b9dda 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -122,6 +122,7 @@ func (dd *Downloader) DownloadModules(modules config.KFDModules, gitPrefix strin src := fmt.Sprintf("git::%s/%s-%s?ref=%s&depth=1", gitPrefix, prefix, name, version) moduleURL := createURL(prefix, name, version) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, moduleURL, nil) if err != nil { return fmt.Errorf("%w '%s' (url: %s): %v", ErrDownloadingModule, name, moduleURL, err) From 5a9638cbb506fed757b620efeef94e6e33fa1a6d Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 15 Jun 2023 18:07:14 +0200 Subject: [PATCH 319/383] feat: skip region validation in test state bucket --- .../common/data/templates/distribution/terraform/main.tf.tpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl index 7bccf0e7f..eda7e01ad 100644 --- a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl +++ b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl @@ -3,6 +3,8 @@ terraform { bucket = "{{ .spec.toolsConfiguration.terraform.state.s3.bucketName }}" key = "{{ .spec.toolsConfiguration.terraform.state.s3.keyPrefix }}/distribution.json" region = "{{ .spec.toolsConfiguration.terraform.state.s3.region }}" + + skip_region_validation = true } } From d2b941a9105eeb33da7f5bf94827b628d192b015 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 20 Jun 2023 10:58:20 +0200 Subject: [PATCH 320/383] feat: add a better default for names_of_kubernetes_cluster_integrated_with_subnets variable --- configs/provisioners/bootstrap/aws/main.tf.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index 544c87bd1..337ddbd46 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -47,7 +47,7 @@ module "vpc" { # availability_zone_names = [] # single_nat_gateway = false # one_nat_gateway_per_az = true - # names_of_kubernetes_cluster_integrated_with_subnets = [] + names_of_kubernetes_cluster_integrated_with_subnets = [var.name] } module "vpn" { From ae15a325139ee77a2e2e3873b261bf2b045e93e5 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 20 Jun 2023 10:58:51 +0200 Subject: [PATCH 321/383] fix: make distribution phase depend on the actual terraform output command instead of output.json --- .../kfd/v1alpha2/eks/create/distribution.go | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 60797071d..49538ae8d 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -498,20 +498,14 @@ func (d *Distribution) createDummyOutput() error { } func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { - var distroOut terraform.OutputJSON - arns := map[string]string{} - distroOutJSON, err := os.ReadFile(path.Join(d.OutputsPath, "output.json")) + out, err := d.tfRunner.Output() if err != nil { - return nil, fmt.Errorf("error reading distribution output: %w", err) - } - - if err := json.Unmarshal(distroOutJSON, &distroOut); err != nil { - return nil, fmt.Errorf("error unmarshaling distribution output: %w", err) + return nil, fmt.Errorf("error getting terraform output: %w", err) } - ebsCsiDriverArn, ok := distroOut["ebs_csi_driver_iam_role_arn"] + ebsCsiDriverArn, ok := out["ebs_csi_driver_iam_role_arn"] if ok { arns["ebs_csi_driver_iam_role_arn"], ok = ebsCsiDriverArn.Value.(string) if !ok { @@ -519,7 +513,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - loadBalancerControllerArn, ok := distroOut["load_balancer_controller_iam_role_arn"] + loadBalancerControllerArn, ok := out["load_balancer_controller_iam_role_arn"] if ok { arns["load_balancer_controller_iam_role_arn"], ok = loadBalancerControllerArn.Value.(string) if !ok { @@ -527,7 +521,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - clusterAutoscalerArn, ok := distroOut["cluster_autoscaler_iam_role_arn"] + clusterAutoscalerArn, ok := out["cluster_autoscaler_iam_role_arn"] if ok { arns["cluster_autoscaler_iam_role_arn"], ok = clusterAutoscalerArn.Value.(string) if !ok { @@ -535,7 +529,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - externalDNSPrivateArn, ok := distroOut["external_dns_private_iam_role_arn"] + externalDNSPrivateArn, ok := out["external_dns_private_iam_role_arn"] if ok { arns["external_dns_private_iam_role_arn"], ok = externalDNSPrivateArn.Value.(string) if !ok { @@ -543,7 +537,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - externalDNSPublicArn, ok := distroOut["external_dns_public_iam_role_arn"] + externalDNSPublicArn, ok := out["external_dns_public_iam_role_arn"] if ok { arns["external_dns_public_iam_role_arn"], ok = externalDNSPublicArn.Value.(string) if !ok { @@ -551,7 +545,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - certManagerArn, ok := distroOut["cert_manager_iam_role_arn"] + certManagerArn, ok := out["cert_manager_iam_role_arn"] if ok { arns["cert_manager_iam_role_arn"], ok = certManagerArn.Value.(string) if !ok { @@ -559,7 +553,7 @@ func (d *Distribution) extractARNsFromTfOut() (map[string]string, error) { } } - veleroArn, ok := distroOut["velero_iam_role_arn"] + veleroArn, ok := out["velero_iam_role_arn"] if ok { arns["velero_iam_role_arn"], ok = veleroArn.Value.(string) if !ok { From 359a2798c5a33ef66e3631ceb7cc1c1c0076417c Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 20 Jun 2023 16:04:26 +0200 Subject: [PATCH 322/383] fix: add missing conditions to kubeconfig flag check --- cmd/create/cluster.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 7db4224ad..23e9616bb 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -78,8 +78,8 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { kubeconfigPath := flags.Kubeconfig if kubeconfigPath != "" && - (flags.Phase != cluster.OperationPhaseDistribution || - flags.SkipPhase != cluster.OperationPhaseKubernetes) { + (flags.Phase != "" && flags.Phase != cluster.OperationPhaseDistribution) || + (flags.SkipPhase != "" && flags.SkipPhase != cluster.OperationPhaseKubernetes) { return fmt.Errorf( "%w: --kubeconfig flag can only be used when running distribution phase alone", ErrParsingFlag, From 12309c07941cb74b3b58e5d26009b3d63991078f Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 20 Jun 2023 17:55:02 +0200 Subject: [PATCH 323/383] feat: introduce handling of skip_region_validation --- configs/provisioners/bootstrap/aws/main.tf.tpl | 2 +- configs/provisioners/cluster/eks/main.tf.tpl | 2 +- go.mod | 2 +- go.sum | 4 ++-- internal/apis/kfd/v1alpha2/eks/create/infrastructure.go | 7 ++++--- internal/apis/kfd/v1alpha2/eks/create/kubernetes.go | 7 ++++--- .../data/templates/distribution/terraform/main.tf.tpl | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index 337ddbd46..76e65578a 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -10,7 +10,7 @@ terraform { key = "{{ .terraform.backend.s3.keyPrefix }}/infrastructure.json" region = "{{ .terraform.backend.s3.region }}" - skip_region_validation = true + skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} } required_providers { diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index 1f1b8934e..dc384a8b3 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -16,7 +16,7 @@ terraform { key = "{{ .terraform.backend.s3.keyPrefix }}/cluster.json" region = "{{ .terraform.backend.s3.region }}" - skip_region_validation = true + skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} } required_providers { diff --git a/go.mod b/go.mod index 4a9f7c0fc..72a115012 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28 + github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index f363d7cd9..990ffff62 100644 --- a/go.sum +++ b/go.sum @@ -296,8 +296,8 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28 h1:+UeK6N3lRfCrOOQLKUKt2Wtnjbb2Rn4TyUt365pBfTg= -github.com/sighupio/fury-distribution v1.25.3-0.20230614133947-7a93ddbdad28/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= +github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d h1:E2hY/mPZL4rHcYLmf4XwGr1IIspbvoTgZsdkLfQoglE= +github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 6fe39f402..f99b97d43 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -202,9 +202,10 @@ func (i *Infrastructure) copyFromTemplate() error { "terraform": { "backend": map[string]any{ "s3": map[string]any{ - "bucketName": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName, - "keyPrefix": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.KeyPrefix, - "region": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + "bucketName": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName, + "keyPrefix": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.KeyPrefix, + "region": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + "skipRegionValidation": i.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.SkipRegionValidation, }, }, }, diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 00d43c74b..15f5c945c 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -359,9 +359,10 @@ func (k *Kubernetes) copyFromTemplate(furyctlCfg template.Config) error { "terraform": { "backend": map[string]any{ "s3": map[string]any{ - "bucketName": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName, - "keyPrefix": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.KeyPrefix, - "region": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + "bucketName": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName, + "keyPrefix": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.KeyPrefix, + "region": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + "skipRegionValidation": k.furyctlConf.Spec.ToolsConfiguration.Terraform.State.S3.SkipRegionValidation, }, }, }, diff --git a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl index eda7e01ad..581c444da 100644 --- a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl +++ b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl @@ -4,7 +4,7 @@ terraform { key = "{{ .spec.toolsConfiguration.terraform.state.s3.keyPrefix }}/distribution.json" region = "{{ .spec.toolsConfiguration.terraform.state.s3.region }}" - skip_region_validation = true + skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} } } From 08ba1b94a1747a8dd249632da7913f26e0dd2505 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 26 Jun 2023 11:29:50 +0200 Subject: [PATCH 324/383] chore: update fury-distribution dependency --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 72a115012..1e5a7810f 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d + github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index 990ffff62..445e0967e 100644 --- a/go.sum +++ b/go.sum @@ -298,6 +298,8 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d h1:E2hY/mPZL4rHcYLmf4XwGr1IIspbvoTgZsdkLfQoglE= github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= +github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e h1:XwQKlhNF1eF9nWl5x5gJtcfh8Xnwg4Yj76v3c0c4exc= +github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 68699fb5b630359ee8b985b1ebd42f41dc6f3c55 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Mon, 26 Jun 2023 11:33:02 +0200 Subject: [PATCH 325/383] chore: update fury-distribution dependency --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1e5a7810f..1fbc216b3 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e + github.com/sighupio/fury-distribution v1.25.3 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index 445e0967e..56913be1e 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d h1:E github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e h1:XwQKlhNF1eF9nWl5x5gJtcfh8Xnwg4Yj76v3c0c4exc= github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= +github.com/sighupio/fury-distribution v1.25.3 h1:BuHanwms4EIkX3UYcyunB7iK1jABFKHdU4ByDryeWBk= +github.com/sighupio/fury-distribution v1.25.3/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From e1f673137b21ec3970486df43e044a1286bad278 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Wed, 7 Jun 2023 14:34:47 +0200 Subject: [PATCH 326/383] feat: added support to new variable bucketNamePrefix (#389) * feat: added support to new variable bucketNamePrefix * fix: linting * fix: e2e tests --- .../provisioners/bootstrap/aws/main.tf.tpl | 2 +- .../provisioners/bootstrap/aws/variables.tf | 6 ++++++ .../kfd/v1alpha2/eks/create/infrastructure.go | 20 +++++++++++++++++++ .../cluster/infrastructure/data/kfd.yaml | 6 +++--- .../create/cluster/kubernetes/data/kfd.yaml | 6 +++--- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index 76e65578a..cbb77fb5a 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -70,6 +70,6 @@ module "vpn" { vpn_dhparams_bits = var.vpn_dhparams_bits vpn_operator_cidrs = var.vpn_operator_cidrs vpn_ssh_users = var.vpn_ssh_users - + vpn_bucket_name_prefix = var.vpn_bucket_name_prefix # vpn_routes = [] } diff --git a/configs/provisioners/bootstrap/aws/variables.tf b/configs/provisioners/bootstrap/aws/variables.tf index c06d73edf..c5dc1d8f3 100644 --- a/configs/provisioners/bootstrap/aws/variables.tf +++ b/configs/provisioners/bootstrap/aws/variables.tf @@ -109,3 +109,9 @@ variable "vpn_ssh_users" { type = list(string) default = [] } + +variable "vpn_bucket_name_prefix" { + type = string + description = "Bucket name prefix for VPN configuration files" + default = "" +} diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index f99b97d43..525e48587 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -237,6 +237,10 @@ func (i *Infrastructure) createTfVars() error { if err := i.addVpnDataToTfVars(&buffer); err != nil { return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) } + + if err := i.addVpnSSHDataToTfVars(&buffer); err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } } return i.writeTfVars(buffer) @@ -381,6 +385,22 @@ func (i *Infrastructure) addVpnDataToTfVars(buffer *bytes.Buffer) error { } } + if i.furyctlConf.Spec.Infrastructure.Vpn.BucketNamePrefix != nil && + *i.furyctlConf.Spec.Infrastructure.Vpn.BucketNamePrefix != "" { + err := bytesx.SafeWriteToBuffer( + buffer, + "vpn_bucket_name_prefix = \"%v\"\n", + *i.furyctlConf.Spec.Infrastructure.Vpn.BucketNamePrefix, + ) + if err != nil { + return fmt.Errorf(SErrWrapWithStr, ErrWritingTfVars, err) + } + } + + return nil +} + +func (i *Infrastructure) addVpnSSHDataToTfVars(buffer *bytes.Buffer) error { if len(i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.AllowedFromCidrs) != 0 { allowedCidrs := make([]string, len(i.furyctlConf.Spec.Infrastructure.Vpn.Ssh.AllowedFromCidrs)) diff --git a/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml index f9c591fe7..f81a8a0e8 100644 --- a/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml +++ b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: develop furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -27,9 +27,9 @@ tools: kubectl: version: 1.25.8 kustomize: - version: 3.5.3 + version: 3.10.0 terraform: - version: 0.15.4 + version: 1.4.6 eks: awscli: version: "*" diff --git a/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml b/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml index f9c591fe7..f81a8a0e8 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml +++ b/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: develop furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 @@ -27,9 +27,9 @@ tools: kubectl: version: 1.25.8 kustomize: - version: 3.5.3 + version: 3.10.0 terraform: - version: 0.15.4 + version: 1.4.6 eks: awscli: version: "*" From d2e9ddd9476243a3b8638aa1ea5d0aa83dc4e301 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 27 Jun 2023 10:12:00 +0200 Subject: [PATCH 327/383] chore: tidy up go mod --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index 56913be1e..d28709c7c 100644 --- a/go.sum +++ b/go.sum @@ -296,10 +296,6 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d h1:E2hY/mPZL4rHcYLmf4XwGr1IIspbvoTgZsdkLfQoglE= -github.com/sighupio/fury-distribution v1.25.3-0.20230620151552-bf1b6f5ebd2d/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= -github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e h1:XwQKlhNF1eF9nWl5x5gJtcfh8Xnwg4Yj76v3c0c4exc= -github.com/sighupio/fury-distribution v1.25.3-0.20230626092152-6bbd75d9569e/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sighupio/fury-distribution v1.25.3 h1:BuHanwms4EIkX3UYcyunB7iK1jABFKHdU4ByDryeWBk= github.com/sighupio/fury-distribution v1.25.3/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= From 4b2219588754cb84dd6c6de30c63d525d8cbdb7c Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 27 Jun 2023 15:59:18 +0200 Subject: [PATCH 328/383] fix: add support for apple git version in tool validation --- internal/dependencies/tools/ansible_test.go | 2 +- internal/dependencies/tools/awscli_test.go | 2 +- internal/dependencies/tools/furyagent_test.go | 2 +- internal/dependencies/tools/git.go | 2 +- internal/dependencies/tools/git_test.go | 82 +++++++++++++++---- internal/dependencies/tools/kubectl_test.go | 2 +- internal/dependencies/tools/kustomize_test.go | 2 +- internal/dependencies/tools/openvpn_test.go | 2 +- internal/dependencies/tools/terraform_test.go | 2 +- internal/dependencies/tools/validator_test.go | 4 +- internal/kubernetes/client_test.go | 2 +- internal/tool/ansible/runner_test.go | 2 +- internal/tool/awscli/runner_test.go | 2 +- internal/tool/furyagent/runner_test.go | 4 +- internal/tool/git/runner_test.go | 2 +- internal/tool/kubectl/runner_test.go | 2 +- internal/tool/kustomize/runner_test.go | 2 +- internal/tool/openvpn/runner_test.go | 4 +- internal/tool/runner_test.go | 2 +- internal/tool/terraform/runner_test.go | 10 +-- internal/x/exec/cmd_test.go | 4 +- internal/x/exec/executor.go | 19 +++-- internal/x/exec/executor_test.go | 2 +- 23 files changed, 111 insertions(+), 48 deletions(-) diff --git a/internal/dependencies/tools/ansible_test.go b/internal/dependencies/tools/ansible_test.go index 3da4dc819..85d5f3b7f 100644 --- a/internal/dependencies/tools/ansible_test.go +++ b/internal/dependencies/tools/ansible_test.go @@ -85,7 +85,7 @@ func Test_Ansible_CheckBinVersion(t *testing.T) { } func newAnsibleRunner() *ansible.Runner { - return ansible.NewRunner(execx.NewFakeExecutor(), ansible.Paths{ + return ansible.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), ansible.Paths{ Ansible: "ansible", }) } diff --git a/internal/dependencies/tools/awscli_test.go b/internal/dependencies/tools/awscli_test.go index c3459ff32..e17d5f4a3 100644 --- a/internal/dependencies/tools/awscli_test.go +++ b/internal/dependencies/tools/awscli_test.go @@ -69,7 +69,7 @@ func Test_Awscli_CheckBinVersion(t *testing.T) { } func newAwscliRunner() *awscli.Runner { - return awscli.NewRunner(execx.NewFakeExecutor(), awscli.Paths{ + return awscli.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), awscli.Paths{ Awscli: "aws", }) } diff --git a/internal/dependencies/tools/furyagent_test.go b/internal/dependencies/tools/furyagent_test.go index b0764d4c4..94d5e8121 100644 --- a/internal/dependencies/tools/furyagent_test.go +++ b/internal/dependencies/tools/furyagent_test.go @@ -128,7 +128,7 @@ func Test_Furyagent_CheckBinVersion(t *testing.T) { } func newFuryagentRunner() *furyagent.Runner { - return furyagent.NewRunner(execx.NewFakeExecutor(), furyagent.Paths{ + return furyagent.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), furyagent.Paths{ Furyagent: "furyagent", }) } diff --git a/internal/dependencies/tools/git.go b/internal/dependencies/tools/git.go index 8389dcc80..e3ddbd396 100644 --- a/internal/dependencies/tools/git.go +++ b/internal/dependencies/tools/git.go @@ -19,7 +19,7 @@ func NewGit(runner *git.Runner, version string) *Git { os: runtime.GOOS, version: version, checker: &checker{ - regex: regexp.MustCompile(`git version .*`), + regex: regexp.MustCompile(`git version v?(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?`), runner: runner, splitFn: func(version string) []string { return strings.Split(version, " ") diff --git a/internal/dependencies/tools/git_test.go b/internal/dependencies/tools/git_test.go index d863658b0..77b259e41 100644 --- a/internal/dependencies/tools/git_test.go +++ b/internal/dependencies/tools/git_test.go @@ -7,6 +7,8 @@ package tools_test import ( + "fmt" + "os" "strings" "testing" @@ -16,7 +18,7 @@ import ( ) func Test_Git_SupportsDownload(t *testing.T) { - a := tools.NewGit(newGitRunner(), "2.39.0") + a := tools.NewGit(newGitRunner("TestHelperProcessGitStandard"), "2.39.0") if a.SupportsDownload() != false { t.Errorf("Git download must not be supported") @@ -27,20 +29,28 @@ func Test_Git_CheckBinVersion(t *testing.T) { t.Parallel() testCases := []struct { - desc string - wantVersion string - wantErr bool - wantErrMsg string + desc string + testHelperProcessFn string + wantVersion string + wantErr bool + wantErrMsg string }{ { - desc: "correct version installed", - wantVersion: "2.39.0", + desc: "correct version installed", + testHelperProcessFn: "TestHelperProcessGitStandard", + wantVersion: "2.39.0", }, { - desc: "wrong version installed", - wantVersion: "2.39.1", - wantErr: true, - wantErrMsg: "installed = 2.39.0, expected = 2.39.1", + desc: "correct version installed, apple flavor", + testHelperProcessFn: "TestHelperProcessGitApple", + wantVersion: "2.37.0", + }, + { + desc: "wrong version installed", + testHelperProcessFn: "TestHelperProcessGitStandard", + wantVersion: "2.39.1", + wantErr: true, + wantErrMsg: "installed = 2.39.0, expected = 2.39.1", }, } for _, tC := range testCases { @@ -49,7 +59,7 @@ func Test_Git_CheckBinVersion(t *testing.T) { t.Run(tC.desc, func(t *testing.T) { t.Parallel() - fa := tools.NewGit(newGitRunner(), tC.wantVersion) + fa := tools.NewGit(newGitRunner(tC.testHelperProcessFn), tC.wantVersion) err := fa.CheckBinVersion() @@ -68,8 +78,52 @@ func Test_Git_CheckBinVersion(t *testing.T) { } } -func newGitRunner() *git.Runner { - return git.NewRunner(execx.NewFakeExecutor(), git.Paths{ +func newGitRunner(testHelperProcessFn string) *git.Runner { + return git.NewRunner(execx.NewFakeExecutor(testHelperProcessFn), git.Paths{ Git: "git", }) } + +func TestHelperProcessGitStandard(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcessGitStandard" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "git": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "git version 2.39.0\n") + } + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} + +func TestHelperProcessGitApple(t *testing.T) { + args := os.Args + + if len(args) < 3 || args[1] != "-test.run=TestHelperProcessGitApple" { + return + } + + cmd, subcmd := args[3], args[4] + + switch cmd { + case "git": + switch subcmd { + case "version": + fmt.Fprintf(os.Stdout, "git version 2.37.0 (Apple Git-136)\n") + } + default: + fmt.Fprintf(os.Stdout, "command not found") + } + + os.Exit(0) +} diff --git a/internal/dependencies/tools/kubectl_test.go b/internal/dependencies/tools/kubectl_test.go index 119b3e8b8..9eb3fc554 100644 --- a/internal/dependencies/tools/kubectl_test.go +++ b/internal/dependencies/tools/kubectl_test.go @@ -125,7 +125,7 @@ func Test_Kubectl_CheckBinVersion(t *testing.T) { } func newKubectlRunner() *kubectl.Runner { - return kubectl.NewRunner(execx.NewFakeExecutor(), kubectl.Paths{ + return kubectl.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), kubectl.Paths{ Kubectl: "kubectl", }, true, true, true) } diff --git a/internal/dependencies/tools/kustomize_test.go b/internal/dependencies/tools/kustomize_test.go index e34ffc74e..415ce7934 100644 --- a/internal/dependencies/tools/kustomize_test.go +++ b/internal/dependencies/tools/kustomize_test.go @@ -128,7 +128,7 @@ func Test_Kustomize_CheckBinVersion(t *testing.T) { } func newKustomizeRunner() *kustomize.Runner { - return kustomize.NewRunner(execx.NewFakeExecutor(), kustomize.Paths{ + return kustomize.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), kustomize.Paths{ Kustomize: "kustomize", }) } diff --git a/internal/dependencies/tools/openvpn_test.go b/internal/dependencies/tools/openvpn_test.go index 7422321b6..7e9581908 100644 --- a/internal/dependencies/tools/openvpn_test.go +++ b/internal/dependencies/tools/openvpn_test.go @@ -89,7 +89,7 @@ func Test_Openvpn_CheckBinVersion(t *testing.T) { } func newOpenvpnRunner() *openvpn.Runner { - return openvpn.NewRunner(execx.NewFakeExecutor(), openvpn.Paths{ + return openvpn.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), openvpn.Paths{ Openvpn: "openvpn", }) } diff --git a/internal/dependencies/tools/terraform_test.go b/internal/dependencies/tools/terraform_test.go index 6878416b7..e36475c21 100644 --- a/internal/dependencies/tools/terraform_test.go +++ b/internal/dependencies/tools/terraform_test.go @@ -128,7 +128,7 @@ func Test_Terraform_CheckBinVersion(t *testing.T) { } func newTerraformRunner() *terraform.Runner { - return terraform.NewRunner(execx.NewFakeExecutor(), terraform.Paths{ + return terraform.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), terraform.Paths{ Terraform: "terraform", }) } diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 357dd3294..9215a2c53 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -134,7 +134,7 @@ func Test_Validator_Validate(t *testing.T) { t.Run(tC.desc, func(t *testing.T) { furyctlPath := path.Join("test_data", "furyctl.yaml") - v := tools.NewValidator(execx.NewFakeExecutor(), "test_data", furyctlPath, false) + v := tools.NewValidator(execx.NewFakeExecutor("TestHelperProcess"), "test_data", furyctlPath, false) oks, errs := v.Validate(tC.manifest, tC.state) @@ -199,7 +199,7 @@ func TestValidator_ValidateBaseReqs(t *testing.T) { t.Run(tC.desc, func(t *testing.T) { furyctlPath := path.Join("test_data", "furyctl.yaml") - v := tools.NewValidator(execx.NewFakeExecutor(), "test_data", furyctlPath, false) + v := tools.NewValidator(execx.NewFakeExecutor("TestHelperProcess"), "test_data", furyctlPath, false) oks, errs := v.ValidateBaseReqs() diff --git a/internal/kubernetes/client_test.go b/internal/kubernetes/client_test.go index 04b65fddd..ed7daf79b 100644 --- a/internal/kubernetes/client_test.go +++ b/internal/kubernetes/client_test.go @@ -248,6 +248,6 @@ func FakeClient(t *testing.T) *kubernetes.Client { true, true, true, - execx.NewFakeExecutor(), + execx.NewFakeExecutor("TestHelperProcess"), ) } diff --git a/internal/tool/ansible/runner_test.go b/internal/tool/ansible/runner_test.go index e54aaebdb..029147cb7 100644 --- a/internal/tool/ansible/runner_test.go +++ b/internal/tool/ansible/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := ansible.NewRunner(execx.NewFakeExecutor(), ansible.Paths{ + r := ansible.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), ansible.Paths{ Ansible: "ansible", WorkDir: os.TempDir(), }) diff --git a/internal/tool/awscli/runner_test.go b/internal/tool/awscli/runner_test.go index e668da973..4c70de4f2 100644 --- a/internal/tool/awscli/runner_test.go +++ b/internal/tool/awscli/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := awscli.NewRunner(execx.NewFakeExecutor(), awscli.Paths{ + r := awscli.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), awscli.Paths{ Awscli: "aws", WorkDir: os.TempDir(), }) diff --git a/internal/tool/furyagent/runner_test.go b/internal/tool/furyagent/runner_test.go index 81e4c3e7e..c35a7e6a9 100644 --- a/internal/tool/furyagent/runner_test.go +++ b/internal/tool/furyagent/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := furyagent.NewRunner(execx.NewFakeExecutor(), furyagent.Paths{ + r := furyagent.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), furyagent.Paths{ Furyagent: "furyagent", WorkDir: os.TempDir(), }) @@ -34,7 +34,7 @@ func Test_Runner_Version(t *testing.T) { } func Test_Runner_ConfigOpenvpnClient(t *testing.T) { - r := furyagent.NewRunner(execx.NewFakeExecutor(), furyagent.Paths{ + r := furyagent.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), furyagent.Paths{ Furyagent: "furyagent", WorkDir: os.TempDir(), }) diff --git a/internal/tool/git/runner_test.go b/internal/tool/git/runner_test.go index 3c108419b..9396e2bd8 100644 --- a/internal/tool/git/runner_test.go +++ b/internal/tool/git/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := git.NewRunner(execx.NewFakeExecutor(), git.Paths{ + r := git.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), git.Paths{ Git: "git", WorkDir: os.TempDir(), }) diff --git a/internal/tool/kubectl/runner_test.go b/internal/tool/kubectl/runner_test.go index 805a0b8d2..5492b00bf 100644 --- a/internal/tool/kubectl/runner_test.go +++ b/internal/tool/kubectl/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := kubectl.NewRunner(execx.NewFakeExecutor(), kubectl.Paths{ + r := kubectl.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), kubectl.Paths{ Kubectl: "kubectl", WorkDir: os.TempDir(), }, true, true, true) diff --git a/internal/tool/kustomize/runner_test.go b/internal/tool/kustomize/runner_test.go index 736727af4..74978c976 100644 --- a/internal/tool/kustomize/runner_test.go +++ b/internal/tool/kustomize/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := kustomize.NewRunner(execx.NewFakeExecutor(), kustomize.Paths{ + r := kustomize.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), kustomize.Paths{ Kustomize: "kustomize", WorkDir: os.TempDir(), }) diff --git a/internal/tool/openvpn/runner_test.go b/internal/tool/openvpn/runner_test.go index 8f4288625..8697e1cc9 100644 --- a/internal/tool/openvpn/runner_test.go +++ b/internal/tool/openvpn/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := openvpn.NewRunner(execx.NewFakeExecutor(), openvpn.Paths{ + r := openvpn.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), openvpn.Paths{ Openvpn: "openvpn", WorkDir: os.TempDir(), }) @@ -34,7 +34,7 @@ func Test_Runner_Version(t *testing.T) { } func Test_Runner_Connect(t *testing.T) { - r := openvpn.NewRunner(execx.NewFakeExecutor(), openvpn.Paths{ + r := openvpn.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), openvpn.Paths{ Openvpn: "openvpn", WorkDir: os.TempDir(), }) diff --git a/internal/tool/runner_test.go b/internal/tool/runner_test.go index 2612f9833..49cc7c05a 100644 --- a/internal/tool/runner_test.go +++ b/internal/tool/runner_test.go @@ -72,7 +72,7 @@ func Test_RunnerFactory_Create(t *testing.T) { t.Run(tC.desc, func(t *testing.T) { t.Parallel() - rf := tool.NewRunnerFactory(execx.NewFakeExecutor(), tool.RunnerFactoryPaths{ + rf := tool.NewRunnerFactory(execx.NewFakeExecutor("TestHelperProcess"), tool.RunnerFactoryPaths{ Bin: os.TempDir(), }) diff --git a/internal/tool/terraform/runner_test.go b/internal/tool/terraform/runner_test.go index 0b1c954ac..1c3173c5f 100644 --- a/internal/tool/terraform/runner_test.go +++ b/internal/tool/terraform/runner_test.go @@ -18,7 +18,7 @@ import ( ) func Test_Runner_Init(t *testing.T) { - r := terraform.NewRunner(execx.NewFakeExecutor(), terraform.Paths{ + r := terraform.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), terraform.Paths{ Terraform: "terraform", WorkDir: test.MkdirTemp(t), }) @@ -37,7 +37,7 @@ func Test_Runner_Plan(t *testing.T) { Plan: test.MkdirTemp(t), } - r := terraform.NewRunner(execx.NewFakeExecutor(), paths) + r := terraform.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), paths) if _, err := r.Plan(42); err != nil { t.Fatal(err) @@ -62,7 +62,7 @@ func Test_Runner_Apply(t *testing.T) { Plan: test.MkdirTemp(t), } - r := terraform.NewRunner(execx.NewFakeExecutor(), paths) + r := terraform.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), paths) if err := r.Apply(42); err != nil { t.Fatal(err) @@ -87,7 +87,7 @@ func Test_Runner_Output(t *testing.T) { Plan: test.MkdirTemp(t), } - r := terraform.NewRunner(execx.NewFakeExecutor(), paths) + r := terraform.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), paths) if _, err := r.Output(); err != nil { t.Fatal(err) @@ -115,7 +115,7 @@ func Test_Runner_Output(t *testing.T) { } func Test_Runner_Version(t *testing.T) { - r := terraform.NewRunner(execx.NewFakeExecutor(), terraform.Paths{ + r := terraform.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), terraform.Paths{ Terraform: "terraform", WorkDir: test.MkdirTemp(t), }) diff --git a/internal/x/exec/cmd_test.go b/internal/x/exec/cmd_test.go index d546465fd..27f7508ad 100644 --- a/internal/x/exec/cmd_test.go +++ b/internal/x/exec/cmd_test.go @@ -46,7 +46,7 @@ func TestNewCmd(t *testing.T) { opts: execx.CmdOptions{ Args: []string{"foo", "bar"}, Err: bytes.NewBufferString("bar"), - Executor: execx.NewFakeExecutor(), + Executor: execx.NewFakeExecutor("TestHelperProcess"), Out: bytes.NewBufferString("foo"), WorkDir: os.TempDir(), }, @@ -137,7 +137,7 @@ func Test_Cmd_Stop(t *testing.T) { desc: "successful stop", cmd: execx.NewCmd("long process", execx.CmdOptions{ Args: []string{"sleep", "60"}, - Executor: execx.NewFakeExecutor(), + Executor: execx.NewFakeExecutor("TestHelperProcess"), }), wantErr: false, }, diff --git a/internal/x/exec/executor.go b/internal/x/exec/executor.go index 85c0f2be2..7305cbb60 100644 --- a/internal/x/exec/executor.go +++ b/internal/x/exec/executor.go @@ -5,6 +5,7 @@ package execx import ( + "fmt" "os" "os/exec" "path/filepath" @@ -24,14 +25,22 @@ func (*StdExecutor) Command(name string, arg ...string) *exec.Cmd { return exec.Command(name, arg...) } -func NewFakeExecutor() *FakeExecutor { - return &FakeExecutor{} +func NewFakeExecutor(testHelperProcessFn string) *FakeExecutor { + if testHelperProcessFn == "" { + testHelperProcessFn = "TestHelperProcess" + } + + return &FakeExecutor{ + testHelperProcessFn: testHelperProcessFn, + } } -type FakeExecutor struct{} +type FakeExecutor struct { + testHelperProcessFn string +} -func (*FakeExecutor) Command(name string, arg ...string) *exec.Cmd { - cs := []string{"-test.run=TestHelperProcess", "--", filepath.Base(name)} +func (fe *FakeExecutor) Command(name string, arg ...string) *exec.Cmd { + cs := []string{fmt.Sprintf("-test.run=%s", fe.testHelperProcessFn), "--", filepath.Base(name)} cs = append(cs, arg...) return exec.Command(os.Args[0], cs...) diff --git a/internal/x/exec/executor_test.go b/internal/x/exec/executor_test.go index 343e70564..3d578a0cb 100644 --- a/internal/x/exec/executor_test.go +++ b/internal/x/exec/executor_test.go @@ -33,7 +33,7 @@ func Test_StdExecutor_Command(t *testing.T) { } func Test_FakeExecutor_Command(t *testing.T) { - e := execx.NewFakeExecutor() + e := execx.NewFakeExecutor("TestHelperProcess") cmd := e.Command("fakectl", "hello world") if cmd == nil { From bbd8438cc35970ab28c679241250fee220d7314c Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 27 Jun 2023 16:08:11 +0200 Subject: [PATCH 329/383] chore: move semver regex to semver package --- internal/dependencies/tools/git.go | 3 ++- internal/semver/regex.go | 3 +++ internal/x/bytes/transform.go | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 internal/semver/regex.go diff --git a/internal/dependencies/tools/git.go b/internal/dependencies/tools/git.go index e3ddbd396..390636f56 100644 --- a/internal/dependencies/tools/git.go +++ b/internal/dependencies/tools/git.go @@ -10,6 +10,7 @@ import ( "runtime" "strings" + "github.com/sighupio/furyctl/internal/semver" "github.com/sighupio/furyctl/internal/tool/git" ) @@ -19,7 +20,7 @@ func NewGit(runner *git.Runner, version string) *Git { os: runtime.GOOS, version: version, checker: &checker{ - regex: regexp.MustCompile(`git version v?(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?`), + regex: regexp.MustCompile(fmt.Sprintf("git version %s", semver.Regex)), runner: runner, splitFn: func(version string) []string { return strings.Split(version, " ") diff --git a/internal/semver/regex.go b/internal/semver/regex.go new file mode 100644 index 000000000..88ad3ab84 --- /dev/null +++ b/internal/semver/regex.go @@ -0,0 +1,3 @@ +package semver + +const Regex string = `v?(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?` //nolint:lll // cannot split regex in multiple lines. diff --git a/internal/x/bytes/transform.go b/internal/x/bytes/transform.go index 3be56ef7a..d2e1b6980 100644 --- a/internal/x/bytes/transform.go +++ b/internal/x/bytes/transform.go @@ -23,7 +23,7 @@ type TransformFunc func([]byte) ([]byte, error) // Author: https://github.com/acarl005 // License: MIT License. -const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" //nolint:lll // Cannot split regex in multiple lines. +const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" //nolint:lll // cannot split regex in multiple lines. var ( ErrJSONTransform = errors.New("error while transform to json") From 7c7a2b708ca0c73ee31a1ded9e117b57c8ce2b2e Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Tue, 27 Jun 2023 16:09:44 +0200 Subject: [PATCH 330/383] fix: add missing license banner --- internal/semver/regex.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/semver/regex.go b/internal/semver/regex.go index 88ad3ab84..05d0ba761 100644 --- a/internal/semver/regex.go +++ b/internal/semver/regex.go @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package semver const Regex string = `v?(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?` //nolint:lll // cannot split regex in multiple lines. From 3d6629e06521a5456fe5ce5ce24fcc18b08906e1 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Thu, 29 Jun 2023 15:05:11 +0200 Subject: [PATCH 331/383] fix: avoid printing 'skip_region_validation' if it is not specified in furyctl.yaml --- configs/provisioners/bootstrap/aws/main.tf.tpl | 4 +++- configs/provisioners/cluster/eks/main.tf.tpl | 4 +++- .../common/data/templates/distribution/terraform/main.tf.tpl | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/configs/provisioners/bootstrap/aws/main.tf.tpl b/configs/provisioners/bootstrap/aws/main.tf.tpl index cbb77fb5a..f83fe7139 100644 --- a/configs/provisioners/bootstrap/aws/main.tf.tpl +++ b/configs/provisioners/bootstrap/aws/main.tf.tpl @@ -10,7 +10,9 @@ terraform { key = "{{ .terraform.backend.s3.keyPrefix }}/infrastructure.json" region = "{{ .terraform.backend.s3.region }}" - skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} + {{- if index .terraform.backend.s3 "skipRegionValidation" }} + skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} + {{- end }} } required_providers { diff --git a/configs/provisioners/cluster/eks/main.tf.tpl b/configs/provisioners/cluster/eks/main.tf.tpl index dc384a8b3..e75d59ffc 100644 --- a/configs/provisioners/cluster/eks/main.tf.tpl +++ b/configs/provisioners/cluster/eks/main.tf.tpl @@ -16,7 +16,9 @@ terraform { key = "{{ .terraform.backend.s3.keyPrefix }}/cluster.json" region = "{{ .terraform.backend.s3.region }}" - skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} + {{- if index .terraform.backend.s3 "skipRegionValidation" }} + skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} + {{- end }} } required_providers { diff --git a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl index 581c444da..81dc37915 100644 --- a/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl +++ b/test/data/expensive/common/data/templates/distribution/terraform/main.tf.tpl @@ -4,7 +4,9 @@ terraform { key = "{{ .spec.toolsConfiguration.terraform.state.s3.keyPrefix }}/distribution.json" region = "{{ .spec.toolsConfiguration.terraform.state.s3.region }}" - skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} + {{- if index .terraform.backend.s3 "skipRegionValidation" }} + skip_region_validation = {{ default false .terraform.backend.s3.skipRegionValidation }} + {{- end }} } } From 8c24b69300b6eeb124d39e357294291fed106d71 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 30 Jun 2023 16:16:38 +0200 Subject: [PATCH 332/383] chore: bump installer version in test furyctl manifests --- test/data/e2e/create/cluster/infrastructure/data/kfd.yaml | 2 +- test/data/e2e/create/cluster/kubernetes/data/kfd.yaml | 2 +- test/data/e2e/create/config/distro/kfd.yaml | 2 +- test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml | 2 +- test/data/e2e/dump/template/complex-dry-run/kfd.yaml | 2 +- test/data/e2e/dump/template/complex/kfd.yaml | 2 +- .../dump/template/distribution-yaml-no-data-property/kfd.yaml | 2 +- test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml | 2 +- test/data/e2e/dump/template/simple-dry-run/kfd.yaml | 2 +- test/data/e2e/dump/template/simple/kfd.yaml | 2 +- test/data/e2e/validate/config/correct/kfd.yaml | 2 +- test/data/e2e/validate/config/wrong/kfd.yaml | 2 +- test/data/e2e/validate/dependencies/correct/kfd.yaml | 2 +- test/data/e2e/validate/dependencies/missing/kfd.yaml | 2 +- test/data/e2e/validate/dependencies/wrong/kfd.yaml | 2 +- test/data/expensive/common/data/kfd.yaml | 2 +- test/data/integration/v1.25.1/distro/kfd.yaml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml index f81a8a0e8..1d36d9026 100644 --- a/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml +++ b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: develop + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml b/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml index f81a8a0e8..1d36d9026 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml +++ b/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: develop + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/create/config/distro/kfd.yaml b/test/data/e2e/create/config/distro/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/create/config/distro/kfd.yaml +++ b/test/data/e2e/create/config/distro/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml b/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml +++ b/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/dump/template/complex/kfd.yaml b/test/data/e2e/dump/template/complex/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/dump/template/complex/kfd.yaml +++ b/test/data/e2e/dump/template/complex/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml +++ b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml +++ b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml +++ b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/dump/template/simple/kfd.yaml b/test/data/e2e/dump/template/simple/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/dump/template/simple/kfd.yaml +++ b/test/data/e2e/dump/template/simple/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/validate/config/correct/kfd.yaml b/test/data/e2e/validate/config/correct/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/validate/config/correct/kfd.yaml +++ b/test/data/e2e/validate/config/correct/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/validate/config/wrong/kfd.yaml b/test/data/e2e/validate/config/wrong/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/validate/config/wrong/kfd.yaml +++ b/test/data/e2e/validate/config/wrong/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/validate/dependencies/correct/kfd.yaml b/test/data/e2e/validate/dependencies/correct/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/validate/dependencies/correct/kfd.yaml +++ b/test/data/e2e/validate/dependencies/correct/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/validate/dependencies/missing/kfd.yaml b/test/data/e2e/validate/dependencies/missing/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/validate/dependencies/missing/kfd.yaml +++ b/test/data/e2e/validate/dependencies/missing/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/e2e/validate/dependencies/wrong/kfd.yaml b/test/data/e2e/validate/dependencies/wrong/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/e2e/validate/dependencies/wrong/kfd.yaml +++ b/test/data/e2e/validate/dependencies/wrong/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/expensive/common/data/kfd.yaml b/test/data/expensive/common/data/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/expensive/common/data/kfd.yaml +++ b/test/data/expensive/common/data/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 diff --git a/test/data/integration/v1.25.1/distro/kfd.yaml b/test/data/integration/v1.25.1/distro/kfd.yaml index f9c591fe7..e29673a88 100644 --- a/test/data/integration/v1.25.1/distro/kfd.yaml +++ b/test/data/integration/v1.25.1/distro/kfd.yaml @@ -15,7 +15,7 @@ modules: kubernetes: eks: version: 1.25 - installer: v2.0.0-alpha.1 + installer: v2.0.0 furyctlSchemas: eks: - apiVersion: kfd.sighup.io/v1alpha2 From cc202f5b8926c258faeacb9f6695170b922fd9d5 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 30 Jun 2023 16:17:07 +0200 Subject: [PATCH 333/383] fix: make use of prepare step --- .drone.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 25f2c5516..9e6deb4a0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,11 +15,16 @@ steps: commands: - mkdir -p .go/cache .go/modcache .go/tmp - go mod download + environment: + CGO_ENABLED: 0 + GOCACHE: /drone/src/.go/cache + GOMODCACHE: /drone/src/.go/modcache + GOTMPDIR: /drone/src/.go/tmp - name: license image: quay.io/sighup/golang:1.20.4 depends_on: - - clone + - prepare pull: always commands: - make license-check From dd464e8455ea3443d2072d7d45d8a6b63cfc33d8 Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 30 Jun 2023 16:17:25 +0200 Subject: [PATCH 334/383] fix: point fury-distribution dep to the right hash --- go.sum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.sum b/go.sum index d28709c7c..cdc8faa14 100644 --- a/go.sum +++ b/go.sum @@ -296,7 +296,7 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.3 h1:BuHanwms4EIkX3UYcyunB7iK1jABFKHdU4ByDryeWBk= +github.com/sighupio/fury-distribution v1.25.3 h1:MLLJhyRgcC0hW2vN0zYZF+hxpvenREIC1AXBkTwagxY= github.com/sighupio/fury-distribution v1.25.3/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= From befe09c6f8355325e1846df9b985179a0f764867 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Fri, 30 Jun 2023 15:10:33 +0200 Subject: [PATCH 335/383] fix: added var at build time --- .goreleaser.yml | 2 +- main.go | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index ebb090a15..bbf55429c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,7 +18,7 @@ builds: - amd64 - arm64 ldflags: - - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} + - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} -X main.mixPanelToken={{.Env.FURYCTL_MIXPANEL_TOKEN}} archives: - name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}" checksum: diff --git a/main.go b/main.go index ed374cd67..79eeb2d1d 100644 --- a/main.go +++ b/main.go @@ -25,11 +25,12 @@ import ( ) var ( - version = "unknown" - gitCommit = "unknown" - buildTime = "unknown" - goVersion = "unknown" - osArch = "unknown" + version = "unknown" + gitCommit = "unknown" + buildTime = "unknown" + goVersion = "unknown" + osArch = "unknown" + mixPanelToken = "" ) func main() { @@ -65,14 +66,12 @@ func exec() int { h = "unknown" } - t := os.Getenv("FURYCTL_MIXPANEL_TOKEN") - // Create the analytics tracker. - a := analytics.NewTracker(t, versions[version], osArch, runtime.GOOS, "SIGHUP", h) + a := analytics.NewTracker(mixPanelToken, versions[version], osArch, runtime.GOOS, "SIGHUP", h) defer a.Flush() - if _, err := cmd.NewRootCommand(versions, logFile, a, t).ExecuteC(); err != nil { + if _, err := cmd.NewRootCommand(versions, logFile, a, mixPanelToken).ExecuteC(); err != nil { log.Error(err) return 1 From 58ef569be529644d31c62c5b3a386f4b72b1e79d Mon Sep 17 00:00:00 2001 From: Claudio Beatrice Date: Fri, 30 Jun 2023 17:18:45 +0200 Subject: [PATCH 336/383] chore: update go deps --- go.mod | 25 +++++++++++---------- go.sum | 69 ++++++++++++++++++++++++---------------------------------- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index 1fbc216b3..dd3067921 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/briandowns/spinner v1.19.0 github.com/denisbrodbeck/machineid v1.0.1 github.com/dukex/mixpanel v1.0.1 - github.com/go-playground/validator/v10 v10.11.1 + github.com/go-playground/validator/v10 v10.14.1 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 github.com/hashicorp/go-getter v1.6.2 @@ -18,12 +18,12 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3 + github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.2 - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -41,10 +41,11 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect @@ -58,7 +59,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.15.13 // indirect - github.com/leodido/go-urn v1.2.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -84,13 +85,13 @@ require ( github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/zclconf/go-cty v1.12.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.4.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.4.0 // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/net v0.11.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.5.0 // indirect - golang.org/x/tools v0.4.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.105.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index cdc8faa14..2c7cf25bd 100644 --- a/go.sum +++ b/go.sum @@ -76,7 +76,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -96,6 +95,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -103,14 +104,13 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -225,16 +225,13 @@ github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexD github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -275,7 +272,6 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -284,9 +280,7 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -296,8 +290,8 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sighupio/fury-distribution v1.25.3 h1:MLLJhyRgcC0hW2vN0zYZF+hxpvenREIC1AXBkTwagxY= -github.com/sighupio/fury-distribution v1.25.3/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= +github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26 h1:kGHZ0NCjijAlDVseu0XJ/WHut9lvcMB9ujLZhWy7fsE= +github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= @@ -320,7 +314,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -368,10 +361,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -382,8 +374,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -409,8 +401,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -446,12 +438,11 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -518,7 +509,6 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -527,8 +517,9 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -543,8 +534,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -597,8 +588,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -705,9 +696,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -718,7 +708,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7edc40fb8bce7d172dcc692bcbab66e77611eb30 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 1 Jun 2023 13:45:26 +0200 Subject: [PATCH 337/383] feat: add support to KFDDistribution kind --- cmd/connect/openvpn.go | 2 +- cmd/create/config.go | 12 +- .../distribution/create/distribution.go | 276 ++++++++++++++++++ .../apis/kfd/v1alpha2/distribution/creator.go | 115 ++++++++ .../apis/kfd/v1alpha2/distribution/init.go | 25 ++ .../apis/kfd/v1alpha2/distribution/schema.go | 11 + .../kfd/v1alpha2/eks/create/distribution.go | 6 +- .../kfd/v1alpha2/eks/create/infrastructure.go | 4 +- .../kfd/v1alpha2/eks/create/kubernetes.go | 4 +- internal/apis/kfd/v1alpha2/eks/creator.go | 14 +- .../kfd/v1alpha2/eks/delete/distribution.go | 2 +- .../kfd/v1alpha2/eks/delete/infrastructure.go | 4 +- .../kfd/v1alpha2/eks/delete/kubernetes.go | 4 +- internal/apis/kfd/v1alpha2/eks/deleter.go | 4 +- internal/apis/kfd/v1alpha2/eks/init.go | 2 +- internal/apis/kfd/v1alpha2/eks/schema.go | 2 +- internal/apis/kfd/v1alpha2/eks/tool.go | 2 +- internal/apis/kfd/v1alpha2/eks/vpn.go | 2 +- internal/apis/schema.go | 8 +- internal/cluster/creator.go | 2 +- internal/cluster/deleter.go | 2 +- internal/cluster/phase.go | 2 +- internal/config/validate.go | 2 +- internal/dependencies/download.go | 2 +- internal/dependencies/tools/validator.go | 2 +- internal/dependencies/tools/validator_test.go | 2 +- internal/dependencies/toolsconf/validator.go | 4 +- internal/distribution/download.go | 2 +- internal/distribution/path.go | 2 +- internal/distribution/path_test.go | 2 +- internal/template/config.go | 27 ++ 31 files changed, 505 insertions(+), 45 deletions(-) create mode 100644 internal/apis/kfd/v1alpha2/distribution/create/distribution.go create mode 100644 internal/apis/kfd/v1alpha2/distribution/creator.go create mode 100644 internal/apis/kfd/v1alpha2/distribution/init.go create mode 100644 internal/apis/kfd/v1alpha2/distribution/schema.go diff --git a/cmd/connect/openvpn.go b/cmd/connect/openvpn.go index 307cb12ba..5c999be2e 100644 --- a/cmd/connect/openvpn.go +++ b/cmd/connect/openvpn.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" cobrax "github.com/sighupio/furyctl/internal/x/cobra" diff --git a/cmd/create/config.go b/cmd/create/config.go index c2ecabe80..c6ff6b828 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" - distroConfig "github.com/sighupio/fury-distribution/pkg/config" + distroConfig "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/cmd/cmdutil" "github.com/sighupio/furyctl/internal/config" @@ -92,10 +92,10 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { }, Spec: distroConfig.FuryctlSpec{ DistributionVersion: semver.EnsurePrefix(version), - ToolsConfiguration: distroConfig.ToolsConfiguration{ - Terraform: distroConfig.Terraform{ - State: distroConfig.State{ - S3: distroConfig.S3{ + ToolsConfiguration: &distroConfig.ToolsConfiguration{ + Terraform: distroConfig.ToolsConfigurationTerraform{ + State: distroConfig.ToolsConfigurationTerrraformState{ + S3: distroConfig.ToolsConfigurationTerrraformStateS3{ BucketName: "bucket-name", KeyPrefix: "key-prefix", Region: "eu-west-1", @@ -203,7 +203,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { "kind", "k", "EKSCluster", - "Type of cluster to create (eg: EKSCluster)", + "Type of cluster to create (eg: EKSCluster, KFDDistribution)", ) cmd.Flags().StringP( diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go new file mode 100644 index 000000000..06c71e190 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -0,0 +1,276 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package create + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/merge" + "github.com/sighupio/furyctl/internal/template" + "github.com/sighupio/furyctl/internal/tool/kubectl" + "github.com/sighupio/furyctl/internal/tool/kustomize" + execx "github.com/sighupio/furyctl/internal/x/exec" + yamlx "github.com/sighupio/furyctl/internal/x/yaml" +) + +const ( + kubectlDelayMaxRetry = 3 + kubectlNoDelayMaxRetry = 7 +) + +var ( + errClusterConnect = errors.New("error connecting to cluster") +) + +type Distribution struct { + *cluster.OperationPhase + furyctlConfPath string + furyctlConf public.KfddistributionKfdV1Alpha2 + distroPath string + kzRunner *kustomize.Runner + kubeRunner *kubectl.Runner + dryRun bool + phase string +} + +func NewDistribution( + paths cluster.CreatorPaths, + furyctlConf public.KfddistributionKfdV1Alpha2, + kfdManifest config.KFD, + dryRun bool, + phase string, +) (*Distribution, error) { + distroDir := path.Join(paths.WorkDir, cluster.OperationPhaseDistribution) + + phaseOp, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, paths.BinPath) + if err != nil { + return nil, fmt.Errorf("error creating distribution phase: %w", err) + } + + return &Distribution{ + OperationPhase: phaseOp, + furyctlConf: furyctlConf, + distroPath: paths.DistroPath, + furyctlConfPath: paths.ConfigPath, + kzRunner: kustomize.NewRunner( + execx.NewStdExecutor(), + kustomize.Paths{ + Kustomize: phaseOp.KustomizePath, + WorkDir: path.Join(phaseOp.Path, "manifests"), + }, + ), + kubeRunner: kubectl.NewRunner( + execx.NewStdExecutor(), + kubectl.Paths{ + Kubectl: phaseOp.KubectlPath, + WorkDir: path.Join(phaseOp.Path, "manifests"), + Kubeconfig: paths.Kubeconfig, + }, + true, + true, + false, + ), + dryRun: dryRun, + phase: phase, + }, nil +} + +func (d *Distribution) Exec() error { + logrus.Info("Installing Kubernetes Fury Distribution...") + + if err := d.CreateFolder(); err != nil { + return fmt.Errorf("error creating distribution phase folder: %w", err) + } + + furyctlMerger, err := d.createFuryctlMerger() + if err != nil { + return err + } + + mCfg, err := template.NewConfigWithoutData(furyctlMerger, []string{"terraform", ".gitignore", "manifests/aws"}) + if err != nil { + return fmt.Errorf("error creating template config: %w", err) + } + + // GENERATE MANIFESTS + outYaml, err := yamlx.MarshalV2(mCfg) + if err != nil { + return fmt.Errorf("error marshaling template config: %w", err) + } + + outDirPath1, err := os.MkdirTemp("", "furyctl-dist-") + if err != nil { + return fmt.Errorf("error creating temp dir: %w", err) + } + + confPath := filepath.Join(outDirPath1, "config.yaml") + + logrus.Debugf("config path = %s", confPath) + + if err = os.WriteFile(confPath, outYaml, os.ModePerm); err != nil { + return fmt.Errorf("error writing config file: %w", err) + } + + templateModel, err := template.NewTemplateModel( + path.Join(d.distroPath, "templates", cluster.OperationPhaseDistribution), + path.Join(d.Path), + confPath, + outDirPath1, + ".tpl", + false, + d.dryRun, + ) + if err != nil { + return fmt.Errorf("error creating template model: %w", err) + } + + err = templateModel.Generate() + if err != nil { + return fmt.Errorf("error generating from template files: %w", err) + } + + // BUILD MANIFESTS + logrus.Info("Building manifests...") + + kzOut, err := d.kzRunner.Build() + if err != nil { + return fmt.Errorf("error building manifests: %w", err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") + if err != nil { + return fmt.Errorf("error creating temp dir: %w", err) + } + + manifestsOutPath := filepath.Join(outDirPath, "out.yaml") + + logrus.Debugf("built manifests = %s", manifestsOutPath) + + if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { + return fmt.Errorf("error writing built manifests: %w", err) + } + + // STOP IF DRY RUN + if d.dryRun { + return nil + } + + // CHECK CLUSTER + logrus.Info("Checking that the cluster is reachable...") + + if _, err := d.kubeRunner.Version(); err != nil { + logrus.Debugf("Got error while running cluster reachability check: %s", err) + + if !d.dryRun { + return errClusterConnect + } + + if d.phase == cluster.OperationPhaseDistribution { + logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + + "running the command without --dry-run") + } + } + + // APPLY MANIFESTS + logrus.Info("Applying manifests...") + + if err := d.delayedApplyRetries(manifestsOutPath, time.Minute, kubectlDelayMaxRetry); err != nil { + return err + } + + if err = d.delayedApplyRetries(manifestsOutPath, 0, kubectlNoDelayMaxRetry); err != nil { + return err + } + + return nil +} + +func (d *Distribution) Stop() error { + return nil +} + +func (d *Distribution) createFuryctlMerger() (*merge.Merger, error) { + defaultsFilePath := path.Join(d.distroPath, "defaults", "kfddistribution-kfd-v1alpha2.yaml") + + defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) + if err != nil { + return &merge.Merger{}, fmt.Errorf("%s - %w", defaultsFilePath, err) + } + + furyctlConf, err := yamlx.FromFileV2[map[any]any](d.furyctlConfPath) + if err != nil { + return &merge.Merger{}, fmt.Errorf("%s - %w", d.furyctlConfPath, err) + } + + furyctlConfMergeModel := merge.NewDefaultModel(furyctlConf, ".spec.distribution") + + merger := merge.NewMerger( + merge.NewDefaultModel(defaultsFile, ".data"), + furyctlConfMergeModel, + ) + + _, err = merger.Merge() + if err != nil { + return nil, fmt.Errorf("error merging furyctl config: %w", err) + } + + reverseMerger := merge.NewMerger( + *merger.GetCustom(), + *merger.GetBase(), + ) + + _, err = reverseMerger.Merge() + if err != nil { + return nil, fmt.Errorf("error merging furyctl config: %w", err) + } + + return reverseMerger, nil +} + +func (d *Distribution) delayedApplyRetries(mPath string, delay time.Duration, maxRetries int) error { + var err error + + retries := 0 + + if maxRetries == 0 { + return nil + } + + err = d.kubeRunner.Apply(mPath) + if err == nil { + return nil + } + + retries++ + + for retries < maxRetries { + t := time.NewTimer(delay) + + if <-t.C; true { + logrus.Debug("applying manifests again... to ensure all resources are created.") + + err = d.kubeRunner.Apply(mPath) + if err == nil { + return nil + } + } + + retries++ + + t.Stop() + } + + return fmt.Errorf("error applying manifests: %w", err) +} diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go new file mode 100644 index 000000000..476be3a34 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -0,0 +1,115 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +import ( + "errors" + "fmt" + "strings" + + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" + "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/distribution/create" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sirupsen/logrus" +) + +var ( + ErrUnsupportedPhase = errors.New("unsupported phase") +) + +type ClusterCreator struct { + paths cluster.CreatorPaths + furyctlConf public.KfddistributionKfdV1Alpha2 + kfdManifest config.KFD + phase string + dryRun bool +} + +func (v *ClusterCreator) SetProperties(props []cluster.CreatorProperty) { + for _, prop := range props { + v.SetProperty(prop.Name, prop.Value) + } +} + +func (v *ClusterCreator) SetProperty(name string, value any) { + switch strings.ToLower(name) { + case cluster.CreatorPropertyConfigPath: + if s, ok := value.(string); ok { + v.paths.ConfigPath = s + } + + case cluster.CreatorPropertyDistroPath: + if s, ok := value.(string); ok { + v.paths.DistroPath = s + } + + case cluster.CreatorPropertyWorkDir: + if s, ok := value.(string); ok { + v.paths.WorkDir = s + } + + case cluster.CreatorPropertyBinPath: + if s, ok := value.(string); ok { + v.paths.BinPath = s + } + + case cluster.CreatorPropertyKubeconfig: + if s, ok := value.(string); ok { + v.paths.Kubeconfig = s + } + + case cluster.CreatorPropertyFuryctlConf: + if s, ok := value.(public.KfddistributionKfdV1Alpha2); ok { + v.furyctlConf = s + } + + case cluster.CreatorPropertyKfdManifest: + if s, ok := value.(config.KFD); ok { + v.kfdManifest = s + } + + case cluster.CreatorPropertyPhase: + if s, ok := value.(string); ok { + v.phase = s + } + + case cluster.CreatorPropertyDryRun: + if b, ok := value.(bool); ok { + v.dryRun = b + } + } +} + +func (v *ClusterCreator) Create(skipPhase string, timeout int) error { + if v.phase != "" && v.phase != cluster.OperationPhaseDistribution { + return ErrUnsupportedPhase + } + + distro, err := create.NewDistribution( + v.paths, + v.furyctlConf, + v.kfdManifest, + v.dryRun, + v.phase, + ) + if err != nil { + return fmt.Errorf("error while initiating distribution phase: %w", err) + } + + if err := distro.Exec(); err != nil { + return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) + } + + if v.dryRun { + logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") + + return nil + } + + logrus.Info("Kubernetes Fury Distribution installed successfully") + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/distribution/init.go b/internal/apis/kfd/v1alpha2/distribution/init.go new file mode 100644 index 000000000..300a11908 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/distribution/init.go @@ -0,0 +1,25 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +import ( + "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" + "github.com/sighupio/furyctl/internal/cluster" +) + +//nolint:gochecknoinits // this pattern requires init function to work. +func init() { + cluster.RegisterCreatorFactory( + "kfd.sighup.io/v1alpha2", + "KFDDistribution", + cluster.NewCreatorFactory[*ClusterCreator, public.KfddistributionKfdV1Alpha2](&ClusterCreator{}), + ) + + // cluster.RegisterDeleterFactory( + // "kfd.sighup.io/v1alpha2", + // "KFDDistribution", + // cluster.NewDeleterFactory[*ClusterDeleter, private.EksclusterKfdV1Alpha2](&ClusterDeleter{}), + // ) +} diff --git a/internal/apis/kfd/v1alpha2/distribution/schema.go b/internal/apis/kfd/v1alpha2/distribution/schema.go new file mode 100644 index 000000000..4de813883 --- /dev/null +++ b/internal/apis/kfd/v1alpha2/distribution/schema.go @@ -0,0 +1,11 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +type ExtraSchemaValidator struct{} + +func (*ExtraSchemaValidator) Validate(confPath string) error { + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 49538ae8d..dc5b45bf5 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -16,8 +16,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" @@ -292,7 +292,7 @@ func (d *Distribution) Stop() error { } func (d *Distribution) createFuryctlMerger() (*merge.Merger, error) { - defaultsFilePath := path.Join(d.distroPath, "furyctl-defaults.yaml") + defaultsFilePath := path.Join(d.distroPath, "defaults", "ekscluster-kfd-v1alpha2.yaml") defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go index 525e48587..fab452fe4 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/create/infrastructure.go @@ -17,8 +17,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/eks" diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 15f5c945c..953cb9e84 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -21,8 +21,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/configs" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/eks" diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 3a61c13c5..abac8c5c5 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -16,8 +16,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/create" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/kubectl" @@ -52,11 +52,6 @@ func (v *ClusterCreator) SetProperty(name string, value any) { lcName := strings.ToLower(name) switch lcName { - case cluster.CreatorPropertyConfigPath: - if s, ok := value.(string); ok { - v.paths.ConfigPath = s - } - case cluster.CreatorPropertyFuryctlConf: if s, ok := value.(private.EksclusterKfdV1Alpha2); ok { v.furyctlConf = s @@ -82,6 +77,11 @@ func (v *ClusterCreator) SetProperty(name string, value any) { v.vpnAutoConnect = b } + case cluster.CreatorPropertyConfigPath: + if s, ok := value.(string); ok { + v.paths.ConfigPath = s + } + case cluster.CreatorPropertyDistroPath: if s, ok := value.(string); ok { v.paths.DistroPath = s diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 501f0a0e5..2cf4ef87e 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/kubernetes" "github.com/sighupio/furyctl/internal/tool/awscli" diff --git a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go index 4d849b23a..71300275a 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/infrastructure.go @@ -11,8 +11,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" diff --git a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go index 8cb9a9e94..f0ea33d57 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/kubernetes.go @@ -13,8 +13,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/terraform" diff --git a/internal/apis/kfd/v1alpha2/eks/deleter.go b/internal/apis/kfd/v1alpha2/eks/deleter.go index bb1272772..5205f4c6e 100644 --- a/internal/apis/kfd/v1alpha2/eks/deleter.go +++ b/internal/apis/kfd/v1alpha2/eks/deleter.go @@ -10,8 +10,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" del "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks/delete" "github.com/sighupio/furyctl/internal/cluster" ) diff --git a/internal/apis/kfd/v1alpha2/eks/init.go b/internal/apis/kfd/v1alpha2/eks/init.go index b34a5e7a3..829fd3524 100644 --- a/internal/apis/kfd/v1alpha2/eks/init.go +++ b/internal/apis/kfd/v1alpha2/eks/init.go @@ -5,7 +5,7 @@ package eks import ( - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/cluster" ) diff --git a/internal/apis/kfd/v1alpha2/eks/schema.go b/internal/apis/kfd/v1alpha2/eks/schema.go index 74789958c..ecb6b0365 100644 --- a/internal/apis/kfd/v1alpha2/eks/schema.go +++ b/internal/apis/kfd/v1alpha2/eks/schema.go @@ -7,7 +7,7 @@ package eks import ( "fmt" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index a1ab19791..1bdeeee0a 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -7,7 +7,7 @@ package eks import ( "fmt" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/tool/openvpn" execx "github.com/sighupio/furyctl/internal/x/exec" yamlx "github.com/sighupio/furyctl/internal/x/yaml" diff --git a/internal/apis/kfd/v1alpha2/eks/vpn.go b/internal/apis/kfd/v1alpha2/eks/vpn.go index 0dd7b4377..731713ad9 100644 --- a/internal/apis/kfd/v1alpha2/eks/vpn.go +++ b/internal/apis/kfd/v1alpha2/eks/vpn.go @@ -18,7 +18,7 @@ import ( "github.com/shirou/gopsutil/v3/process" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/schema/private" + "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/furyagent" "github.com/sighupio/furyctl/internal/tool/openvpn" diff --git a/internal/apis/schema.go b/internal/apis/schema.go index ed8e523d4..7379b40be 100644 --- a/internal/apis/schema.go +++ b/internal/apis/schema.go @@ -4,7 +4,10 @@ package apis -import "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" +import ( + "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/distribution" + "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/eks" +) type ExtraSchemaValidator interface { Validate(confPath string) error @@ -17,6 +20,9 @@ func NewExtraSchemaValidatorFactory(apiVersion, kind string) ExtraSchemaValidato case "EKSCluster": return &eks.ExtraSchemaValidator{} + case "KFDDistribution": + return &distribution.ExtraSchemaValidator{} + default: return nil } diff --git a/internal/cluster/creator.go b/internal/cluster/creator.go index d1685facd..3069f0e5c 100644 --- a/internal/cluster/creator.go +++ b/internal/cluster/creator.go @@ -9,7 +9,7 @@ import ( "fmt" "strings" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) diff --git a/internal/cluster/deleter.go b/internal/cluster/deleter.go index 597f6675a..53525b9c4 100644 --- a/internal/cluster/deleter.go +++ b/internal/cluster/deleter.go @@ -8,7 +8,7 @@ import ( "fmt" "strings" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) diff --git a/internal/cluster/phase.go b/internal/cluster/phase.go index c954add99..ee1eeee71 100644 --- a/internal/cluster/phase.go +++ b/internal/cluster/phase.go @@ -10,7 +10,7 @@ import ( "os" "path" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/template" iox "github.com/sighupio/furyctl/internal/x/io" yamlx "github.com/sighupio/furyctl/internal/x/yaml" diff --git a/internal/config/validate.go b/internal/config/validate.go index fc1238683..7a388d2cb 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/analytics" "github.com/sighupio/furyctl/internal/apis" "github.com/sighupio/furyctl/internal/distribution" diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index c652b9dda..192fd3466 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/dependencies/tools" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index 11514ab2e..7b64c1a66 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -9,7 +9,7 @@ import ( "reflect" "strings" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/apis" itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 9215a2c53..fc2314c83 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/dependencies/tools" execx "github.com/sighupio/furyctl/internal/x/exec" ) diff --git a/internal/dependencies/toolsconf/validator.go b/internal/dependencies/toolsconf/validator.go index a0142723f..1f6c5b5f5 100644 --- a/internal/dependencies/toolsconf/validator.go +++ b/internal/dependencies/toolsconf/validator.go @@ -10,7 +10,7 @@ import ( "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/tool/awscli" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -36,7 +36,7 @@ type Validator struct { awsCliRunner *awscli.Runner } -func (v *Validator) Validate(s3Conf config.S3) ([]string, []error) { +func (v *Validator) Validate(s3Conf config.ToolsConfigurationTerrraformStateS3) ([]string, []error) { return v.checkAWSS3Bucket(s3Conf.BucketName, s3Conf.Region) } diff --git a/internal/distribution/download.go b/internal/distribution/download.go index 6688e7c05..898b0df86 100644 --- a/internal/distribution/download.go +++ b/internal/distribution/download.go @@ -14,7 +14,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/sirupsen/logrus" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" netx "github.com/sighupio/furyctl/internal/x/net" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) diff --git a/internal/distribution/path.go b/internal/distribution/path.go index 34748ff1e..10bab94e2 100644 --- a/internal/distribution/path.go +++ b/internal/distribution/path.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" ) const ValidLength = 2 diff --git a/internal/distribution/path_test.go b/internal/distribution/path_test.go index 6fc7ce82a..d83ae4f42 100644 --- a/internal/distribution/path_test.go +++ b/internal/distribution/path_test.go @@ -11,7 +11,7 @@ import ( "path/filepath" "testing" - "github.com/sighupio/fury-distribution/pkg/config" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/distribution" ) diff --git a/internal/template/config.go b/internal/template/config.go index 454cd8e1d..467aba60b 100644 --- a/internal/template/config.go +++ b/internal/template/config.go @@ -67,6 +67,33 @@ func NewConfig(tplSource, data *merge.Merger, excluded []string) (Config, error) return cfg, nil } +func NewConfigWithoutData(tplSource *merge.Merger, excluded []string) (Config, error) { + var cfg Config + + if *tplSource.GetCustom() == nil { + return cfg, ErrTemplateSourceCustomIsNil + } + + tmpl := Templates{} + + mergedTmpl, ok := (*tplSource.GetCustom()).Content()["templates"] + if ok { + tmplMap, err := newTemplatesFromMap(mergedTmpl) + if err != nil { + return cfg, err + } + + tmpl = *tmplMap + } + + tmpl.Excludes = append(tmpl.Excludes, excluded...) + + cfg.Templates = tmpl + cfg.Include = nil + + return cfg, nil +} + func newTemplatesFromMap(t any) (*Templates, error) { var exc []string From 3fd7933df27746e8ae5a4bad8a7046c75bc0f0ad Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 1 Jun 2023 13:48:44 +0200 Subject: [PATCH 338/383] fix: use dev fury-distribution version --- go.sum | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/go.sum b/go.sum index 2c7cf25bd..b2bf31333 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= github.com/briandowns/spinner v1.19.0/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -75,6 +76,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -88,6 +91,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -164,6 +168,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -174,6 +179,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= From 3e755356a33377983ef244106a727b4a705ce02b Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 1 Jun 2023 15:06:15 +0200 Subject: [PATCH 339/383] fix: config data merge --- internal/template/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/template/config.go b/internal/template/config.go index 467aba60b..66dfea4f0 100644 --- a/internal/template/config.go +++ b/internal/template/config.go @@ -88,7 +88,10 @@ func NewConfigWithoutData(tplSource *merge.Merger, excluded []string) (Config, e tmpl.Excludes = append(tmpl.Excludes, excluded...) + builder := mapx.NewBuilder(false) + cfg.Templates = tmpl + cfg.Data = builder.ToMapStringAny((*tplSource.GetBase()).Content()) cfg.Include = nil return cfg, nil From 151779276203881a8282904fbab6a08817783d68 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 6 Jun 2023 10:07:21 +0200 Subject: [PATCH 340/383] feat: check that toolsConfiguration is not nil before deps validation --- internal/dependencies/tools/validator.go | 2 +- internal/dependencies/validate.go | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index 7b64c1a66..b69a34b67 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -77,7 +77,7 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( } } - if miniConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName != "" { + if miniConf.Spec.ToolsConfiguration != nil && miniConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName != "" { tool := tv.toolFactory.Create(itool.Awscli, "*") if err := tool.CheckBinVersion(); err != nil { errs = append(errs, err) diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index 89bdf0e23..1606e7b4d 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -55,10 +55,12 @@ func (v *Validator) Validate(res distribution.DownloadResult) error { return fmt.Errorf("%w: %v", errValidatingEnv, errs) } - if _, errs := v.infraValidator.Validate( - res.MinimalConf.Spec.ToolsConfiguration.Terraform.State.S3, - ); len(errs) > 0 { - return fmt.Errorf("%w: %v", errValidatingToolsConf, errs) + if res.MinimalConf.Spec.ToolsConfiguration != nil { + if _, errs := v.infraValidator.Validate( + res.MinimalConf.Spec.ToolsConfiguration.Terraform.State.S3, + ); len(errs) > 0 { + return fmt.Errorf("%w: %v", errValidatingToolsConf, errs) + } } return nil From a5b307ff000955b41fcb5ffd0a8e4bc323630b10 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 6 Jun 2023 10:09:02 +0200 Subject: [PATCH 341/383] lint: run formatters --- .../apis/kfd/v1alpha2/distribution/create/distribution.go | 4 +--- internal/apis/kfd/v1alpha2/distribution/creator.go | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 06c71e190..b54755774 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -30,9 +30,7 @@ const ( kubectlNoDelayMaxRetry = 7 ) -var ( - errClusterConnect = errors.New("error connecting to cluster") -) +var errClusterConnect = errors.New("error connecting to cluster") type Distribution struct { *cluster.OperationPhase diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go index 476be3a34..3905fc5a0 100644 --- a/internal/apis/kfd/v1alpha2/distribution/creator.go +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -9,16 +9,15 @@ import ( "fmt" "strings" + "github.com/sirupsen/logrus" + "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/distribution/create" "github.com/sighupio/furyctl/internal/cluster" - "github.com/sirupsen/logrus" ) -var ( - ErrUnsupportedPhase = errors.New("unsupported phase") -) +var ErrUnsupportedPhase = errors.New("unsupported phase") type ClusterCreator struct { paths cluster.CreatorPaths From 64b51c13848c407ef97a84b68c5e8c20ac1b5f42 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 6 Jun 2023 12:04:30 +0200 Subject: [PATCH 342/383] feat: check storage class and node readiness before apply --- .../distribution/create/distribution.go | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index b54755774..2d81a289f 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -30,7 +30,11 @@ const ( kubectlNoDelayMaxRetry = 7 ) -var errClusterConnect = errors.New("error connecting to cluster") +var ( + errClusterConnect = errors.New("error connecting to cluster") + errNoStorageClass = errors.New("at least one storage class is required") + errNodesNotReady = errors.New("all nodes should be Ready") +) type Distribution struct { *cluster.OperationPhase @@ -102,7 +106,7 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error creating template config: %w", err) } - // GENERATE MANIFESTS + // Generate manifests. outYaml, err := yamlx.MarshalV2(mCfg) if err != nil { return fmt.Errorf("error marshaling template config: %w", err) @@ -139,7 +143,7 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error generating from template files: %w", err) } - // BUILD MANIFESTS + // Build manifests. logrus.Info("Building manifests...") kzOut, err := d.kzRunner.Build() @@ -160,12 +164,12 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error writing built manifests: %w", err) } - // STOP IF DRY RUN + // Stop if dry run is enabled. if d.dryRun { return nil } - // CHECK CLUSTER + // Check cluster connection and requirements. logrus.Info("Checking that the cluster is reachable...") if _, err := d.kubeRunner.Version(); err != nil { @@ -181,7 +185,27 @@ func (d *Distribution) Exec() error { } } - // APPLY MANIFESTS + logrus.Info("Checking if at least one storage class is available...") + getStorageClassesOutput, err := d.kubeRunner.Get("", "storageclasses") + if err != nil { + return fmt.Errorf("error while checking storage class: %w", err) + } + + if getStorageClassesOutput == "No resources found" { + return errNoStorageClass + } + + logrus.Info("Checking if all nodes are ready...") + getNotReadyNodesOutput, err := d.kubeRunner.Get("", "nodes", "--output", "jsonpath=\"{range .items[?(@.status.conditions[-1].type=='NotReady')]}{.metadata.name} {.status.conditions[-1].type}{'\\n'}{end}\"") + if err != nil { + return fmt.Errorf("error while checking nodes: %w", err) + } + + if getNotReadyNodesOutput != "\"\"" { + return errNodesNotReady + } + + // Apply manifests. logrus.Info("Applying manifests...") if err := d.delayedApplyRetries(manifestsOutPath, time.Minute, kubectlDelayMaxRetry); err != nil { From 58553a34fdbc41c16e5b118be140b86318ce0654 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 6 Jun 2023 12:10:43 +0200 Subject: [PATCH 343/383] lint: address linter issues --- .../distribution/create/distribution.go | 18 +++++++++++------- .../apis/kfd/v1alpha2/distribution/creator.go | 2 +- .../apis/kfd/v1alpha2/distribution/init.go | 6 ------ .../apis/kfd/v1alpha2/distribution/schema.go | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 2d81a289f..72f5b6c74 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -186,6 +186,7 @@ func (d *Distribution) Exec() error { } logrus.Info("Checking if at least one storage class is available...") + getStorageClassesOutput, err := d.kubeRunner.Get("", "storageclasses") if err != nil { return fmt.Errorf("error while checking storage class: %w", err) @@ -196,7 +197,14 @@ func (d *Distribution) Exec() error { } logrus.Info("Checking if all nodes are ready...") - getNotReadyNodesOutput, err := d.kubeRunner.Get("", "nodes", "--output", "jsonpath=\"{range .items[?(@.status.conditions[-1].type=='NotReady')]}{.metadata.name} {.status.conditions[-1].type}{'\\n'}{end}\"") + + getNotReadyNodesOutput, err := d.kubeRunner.Get( + "", + "nodes", + "--output", + //nolint:lll // string needed as is + "jsonpath=\"{range .items[?(@.status.conditions[-1].type=='NotReady')]}{.metadata.name} {.status.conditions[-1].type}{'\\n'}{end}\"", + ) if err != nil { return fmt.Errorf("error while checking nodes: %w", err) } @@ -212,14 +220,10 @@ func (d *Distribution) Exec() error { return err } - if err = d.delayedApplyRetries(manifestsOutPath, 0, kubectlNoDelayMaxRetry); err != nil { - return err - } - - return nil + return d.delayedApplyRetries(manifestsOutPath, 0, kubectlNoDelayMaxRetry) } -func (d *Distribution) Stop() error { +func (*Distribution) Stop() error { return nil } diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go index 3905fc5a0..e51a18f7a 100644 --- a/internal/apis/kfd/v1alpha2/distribution/creator.go +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -82,7 +82,7 @@ func (v *ClusterCreator) SetProperty(name string, value any) { } } -func (v *ClusterCreator) Create(skipPhase string, timeout int) error { +func (v *ClusterCreator) Create(_ string, _ int) error { if v.phase != "" && v.phase != cluster.OperationPhaseDistribution { return ErrUnsupportedPhase } diff --git a/internal/apis/kfd/v1alpha2/distribution/init.go b/internal/apis/kfd/v1alpha2/distribution/init.go index 300a11908..769d06ede 100644 --- a/internal/apis/kfd/v1alpha2/distribution/init.go +++ b/internal/apis/kfd/v1alpha2/distribution/init.go @@ -16,10 +16,4 @@ func init() { "KFDDistribution", cluster.NewCreatorFactory[*ClusterCreator, public.KfddistributionKfdV1Alpha2](&ClusterCreator{}), ) - - // cluster.RegisterDeleterFactory( - // "kfd.sighup.io/v1alpha2", - // "KFDDistribution", - // cluster.NewDeleterFactory[*ClusterDeleter, private.EksclusterKfdV1Alpha2](&ClusterDeleter{}), - // ) } diff --git a/internal/apis/kfd/v1alpha2/distribution/schema.go b/internal/apis/kfd/v1alpha2/distribution/schema.go index 4de813883..31b31abb2 100644 --- a/internal/apis/kfd/v1alpha2/distribution/schema.go +++ b/internal/apis/kfd/v1alpha2/distribution/schema.go @@ -6,6 +6,6 @@ package distribution type ExtraSchemaValidator struct{} -func (*ExtraSchemaValidator) Validate(confPath string) error { +func (*ExtraSchemaValidator) Validate(_ string) error { return nil } From b9019f85ce9cb3dde677eaac53beba8a95fbe93b Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 6 Jun 2023 12:22:05 +0200 Subject: [PATCH 344/383] tests: fix validator tests --- internal/dependencies/tools/validator_test.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index fc2314c83..2cf7bb6ce 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -39,10 +39,10 @@ func Test_Validator_Validate(t *testing.T) { }, state: config.Furyctl{ Spec: config.FuryctlSpec{ - ToolsConfiguration: config.ToolsConfiguration{ - Terraform: config.Terraform{ - State: config.State{ - S3: config.S3{ + ToolsConfiguration: &config.ToolsConfiguration{ + Terraform: config.ToolsConfigurationTerraform{ + State: config.ToolsConfigurationTerrraformState{ + S3: config.ToolsConfigurationTerrraformStateS3{ BucketName: "test", }, }, @@ -72,10 +72,10 @@ func Test_Validator_Validate(t *testing.T) { }, state: config.Furyctl{ Spec: config.FuryctlSpec{ - ToolsConfiguration: config.ToolsConfiguration{ - Terraform: config.Terraform{ - State: config.State{ - S3: config.S3{ + ToolsConfiguration: &config.ToolsConfiguration{ + Terraform: config.ToolsConfigurationTerraform{ + State: config.ToolsConfigurationTerrraformState{ + S3: config.ToolsConfigurationTerrraformStateS3{ BucketName: "test", }, }, @@ -107,10 +107,10 @@ func Test_Validator_Validate(t *testing.T) { APIVersion: "kfd.sighup.io/v1alpha2", Kind: "EKSCluster", Spec: config.FuryctlSpec{ - ToolsConfiguration: config.ToolsConfiguration{ - Terraform: config.Terraform{ - State: config.State{ - S3: config.S3{ + ToolsConfiguration: &config.ToolsConfiguration{ + Terraform: config.ToolsConfigurationTerraform{ + State: config.ToolsConfigurationTerrraformState{ + S3: config.ToolsConfigurationTerrraformStateS3{ BucketName: "test", }, }, From c56d1fe78552e53d94a78f5306de01cfdae5f7b2 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 6 Jun 2023 13:21:15 +0200 Subject: [PATCH 345/383] feat: change node readiness check command --- internal/apis/kfd/v1alpha2/distribution/create/distribution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 72f5b6c74..169a680df 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -203,7 +203,7 @@ func (d *Distribution) Exec() error { "nodes", "--output", //nolint:lll // string needed as is - "jsonpath=\"{range .items[?(@.status.conditions[-1].type=='NotReady')]}{.metadata.name} {.status.conditions[-1].type}{'\\n'}{end}\"", + "jsonpath=\"{range .items[*]}{.spec.taints[?(@.key==\"node.kubernetes.io/not-ready\")]}{end}\"", ) if err != nil { return fmt.Errorf("error while checking nodes: %w", err) From 687ee641d89c37870c564c36649b598d5935132b Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 12 Jun 2023 21:06:54 +0200 Subject: [PATCH 346/383] feat: validate dependencies based on kind --- cmd/create/config.go | 11 --- cmd/validate/dependencies.go | 12 ---- go.sum | 6 -- internal/apis/kfd/v1alpha2/eks/tool.go | 41 ++++++++++- internal/dependencies/download.go | 14 ++-- internal/dependencies/tools/tool.go | 21 +++--- internal/dependencies/tools/validator.go | 69 +++++++++++------- internal/dependencies/toolsconf/validator.go | 71 ------------------- internal/dependencies/validate.go | 11 --- internal/tool/runner.go | 74 ++++++++++---------- 10 files changed, 139 insertions(+), 191 deletions(-) delete mode 100644 internal/dependencies/toolsconf/validator.go diff --git a/cmd/create/config.go b/cmd/create/config.go index c6ff6b828..8b43e6bca 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -92,17 +92,6 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { }, Spec: distroConfig.FuryctlSpec{ DistributionVersion: semver.EnsurePrefix(version), - ToolsConfiguration: &distroConfig.ToolsConfiguration{ - Terraform: distroConfig.ToolsConfigurationTerraform{ - State: distroConfig.ToolsConfigurationTerrraformState{ - S3: distroConfig.ToolsConfigurationTerrraformStateS3{ - BucketName: "bucket-name", - KeyPrefix: "key-prefix", - Region: "eu-west-1", - }, - }, - }, - }, }, } diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index 64e992ecd..e0fd21f81 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -17,7 +17,6 @@ import ( "github.com/sighupio/furyctl/internal/dependencies" "github.com/sighupio/furyctl/internal/dependencies/envvars" "github.com/sighupio/furyctl/internal/dependencies/tools" - "github.com/sighupio/furyctl/internal/dependencies/toolsconf" "github.com/sighupio/furyctl/internal/distribution" cobrax "github.com/sighupio/furyctl/internal/x/cobra" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -88,11 +87,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { }) toolsValidator := tools.NewValidator(executor, binPath, furyctlPath, false) - envVarsValidator := envvars.NewValidator() - - toolsConfigValidator := toolsconf.NewValidator(executor) - errs := make([]error, 0) logrus.Info("Validating tools...") @@ -108,11 +103,8 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { logrus.Info("Validating tools configuration...") - tcoks, tcerrs := toolsConfigValidator.Validate(dres.MinimalConf.Spec.ToolsConfiguration.Terraform.State.S3) - errs = append(errs, terrs...) errs = append(errs, eerrs...) - errs = append(errs, tcerrs...) for _, tok := range toks { logrus.Infof("%s: binary found in vendor folder", tok) @@ -122,10 +114,6 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { logrus.Infof("%s: environment variable found", eok) } - for _, tcok := range tcoks { - logrus.Infof("%s: configured", tcok) - } - if len(errs) > 0 { logrus.Debugf("Repository path: %s", dres.RepoPath) diff --git a/go.sum b/go.sum index b2bf31333..2c7cf25bd 100644 --- a/go.sum +++ b/go.sum @@ -67,7 +67,6 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= github.com/briandowns/spinner v1.19.0/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -76,8 +75,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -91,7 +88,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -168,7 +164,6 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -179,7 +174,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index 1bdeeee0a..30e169525 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -5,15 +5,21 @@ package eks import ( + "errors" "fmt" "github.com/sighupio/fury-distribution/pkg/apis/ekscluster/v1alpha2/private" + "github.com/sighupio/furyctl/internal/tool/awscli" "github.com/sighupio/furyctl/internal/tool/openvpn" execx "github.com/sighupio/furyctl/internal/x/exec" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -var ErrOpenVPNNotInstalled = fmt.Errorf("openvpn is not installed") +var ( + ErrOpenVPNNotInstalled = errors.New("openvpn is not installed") + ErrAWSS3BucketNotFound = errors.New("AWS S3 Bucket not found, please create it before running furyctl") + ErrAWSS3BucketRegionMismatch = errors.New("AWS S3 Bucket region mismatch") +) type ExtraToolsValidator struct { executor execx.Executor @@ -44,6 +50,12 @@ func (x *ExtraToolsValidator) Validate(confPath string) ([]string, []error) { oks = append(oks, "openvpn") } + if err := x.terraformStateAWSS3Bucket(furyctlConf); err != nil { + errs = append(errs, err) + } else { + oks = append(oks, "terraform state aws s3 bucket") + } + return oks, errs } @@ -65,3 +77,30 @@ func (x *ExtraToolsValidator) openVPN(conf private.EksclusterKfdV1Alpha2) error return nil } + +func (x *ExtraToolsValidator) terraformStateAWSS3Bucket(conf private.EksclusterKfdV1Alpha2) error { + awsCliRunner := awscli.NewRunner( + x.executor, + awscli.Paths{ + Awscli: "aws", + WorkDir: "", + }, + ) + + r, err := awsCliRunner.S3Api("get-bucket-location", "--bucket", string(conf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName), "--output", "text") + if err != nil { + return ErrAWSS3BucketNotFound + } + + // AWS S3 Bucket in us-east-1 region returns None as LocationConstraint + // https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-location.html#output + if r == "None" { + r = "us-east-1" + } + + if r != string(conf.Spec.ToolsConfiguration.Terraform.State.S3.Region) { + return fmt.Errorf("%w, expected %s, got %s", ErrAWSS3BucketRegionMismatch, conf.Spec.ToolsConfiguration.Terraform.State.S3.Region, r) + } + + return nil +} diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 192fd3466..cfb05d9e3 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -20,6 +20,7 @@ import ( "github.com/sighupio/furyctl/internal/dependencies/tools" "github.com/sighupio/furyctl/internal/distribution" "github.com/sighupio/furyctl/internal/semver" + "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" netx "github.com/sighupio/furyctl/internal/x/net" @@ -221,20 +222,24 @@ func (dd *Downloader) DownloadTools(kfdTools config.KFDTools) ([]string, error) for j := 0; j < tls.Field(i).NumField(); j++ { name := strings.ToLower(tls.Field(i).Type().Field(j).Name) - version, ok := tls.Field(i).Field(j).Interface().(config.Tool) + toolCfg, ok := tls.Field(i).Field(j).Interface().(config.KFDTool) if !ok { return unsupportedTools, fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) } - tool := dd.toolFactory.Create(name, version.String()) + tool, err := dd.toolFactory.Create(tool.ToolName(name), toolCfg.Version) + if err != nil { + return unsupportedTools, fmt.Errorf("%s: %w", name, err) + } + if tool == nil || !tool.SupportsDownload() { unsupportedTools = append(unsupportedTools, name) continue } - dst := filepath.Join(dd.binPath, name, version.String()) + dst := filepath.Join(dd.binPath, name, toolCfg.Version) if err := dd.client.Download(tool.SrcPath(), dst); err != nil { return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, tool.SrcPath(), err) @@ -244,8 +249,7 @@ func (dd *Downloader) DownloadTools(kfdTools config.KFDTools) ([]string, error) return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrRenamingFile, tool.SrcPath(), err) } - err := os.Chmod(filepath.Join(dst, name), iox.FullPermAccess) - if err != nil { + if err := os.Chmod(filepath.Join(dst, name), iox.FullPermAccess); err != nil { return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrChangingFilePermissions, tool.SrcPath(), err) } } diff --git a/internal/dependencies/tools/tool.go b/internal/dependencies/tools/tool.go index 5a1ee066a..1c68130dd 100644 --- a/internal/dependencies/tools/tool.go +++ b/internal/dependencies/tools/tool.go @@ -31,6 +31,7 @@ var ( errCannotParse = errors.New("can't parse system tool version") errMissingBin = errors.New("missing binary from vendor folder") errGetVersion = errors.New("can't get tool version") + errUnknowToolName = errors.New("unknow tool name") ) type Tool interface { @@ -60,7 +61,7 @@ type Factory struct { runnerFactory *tool.RunnerFactory } -func (f *Factory) Create(name, version string) Tool { +func (f *Factory) Create(name tool.ToolName, version string) (Tool, error) { t := f.runnerFactory.Create(name, version, "") if name == tool.Ansible { @@ -69,7 +70,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected ansible.Runner, got %T", t)) } - return NewAnsible(a, version) + return NewAnsible(a, version), nil } if name == tool.Awscli { @@ -78,7 +79,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected awscli.Runner, got %T", t)) } - return NewAwscli(a, version) + return NewAwscli(a, version), nil } if name == tool.Furyagent { @@ -87,7 +88,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected furyagent.Runner, got %T", t)) } - return NewFuryagent(fa, version) + return NewFuryagent(fa, version), nil } if name == tool.Git { @@ -96,7 +97,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected git.Runner, got %T", t)) } - return NewGit(g, version) + return NewGit(g, version), nil } if name == tool.Kubectl { @@ -105,7 +106,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected kubectl.Runner, got %T", t)) } - return NewKubectl(k, version) + return NewKubectl(k, version), nil } if name == tool.Kustomize { @@ -114,7 +115,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected kustomize.Runner, got %T", t)) } - return NewKustomize(k, version) + return NewKustomize(k, version), nil } if name == tool.Openvpn { @@ -123,7 +124,7 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected openvpn.Runner, got %T", t)) } - return NewOpenvpn(o, version) + return NewOpenvpn(o, version), nil } if name == tool.Terraform { @@ -132,10 +133,10 @@ func (f *Factory) Create(name, version string) Tool { panic(fmt.Sprintf("expected terraform.Runner, got %T", t)) } - return NewTerraform(tf, version) + return NewTerraform(tf, version), nil } - return nil + return nil, errUnknowToolName } type checker struct { diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index b69a34b67..1d75ef7d0 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -11,6 +11,7 @@ import ( "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/apis" + "github.com/sighupio/furyctl/internal/tool" itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -41,7 +42,11 @@ func (tv *Validator) ValidateBaseReqs() ([]string, []error) { errs []error ) - git := tv.toolFactory.Create(itool.Git, "*") + git, err := tv.toolFactory.Create(itool.Git, "*") + if err != nil { + errs = append(errs, err) + } + if err := git.CheckBinVersion(); err != nil { errs = append(errs, err) } else { @@ -57,33 +62,16 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( errs []error ) - tls := reflect.ValueOf(kfdManifest.Tools) - for i := 0; i < tls.NumField(); i++ { - for j := 0; j < tls.Field(i).NumField(); j++ { - if version, ok := tls.Field(i).Field(j).Interface().(config.Tool); ok { - if version.String() == "" { - continue - } - - name := strings.ToLower(tls.Field(i).Type().Field(j).Name) - - tool := tv.toolFactory.Create(name, version.String()) - if err := tool.CheckBinVersion(); err != nil { - errs = append(errs, err) - } else { - oks = append(oks, name) - } - } - } - } + // Validate common tools. + cOks, cErrs := tv.validateTools(kfdManifest.Tools.Common) + oks = append(oks, cOks...) + errs = append(errs, cErrs...) - if miniConf.Spec.ToolsConfiguration != nil && miniConf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName != "" { - tool := tv.toolFactory.Create(itool.Awscli, "*") - if err := tool.CheckBinVersion(); err != nil { - errs = append(errs, err) - } else { - oks = append(oks, "aws") - } + // Validate eks tools only if kind is EKSCluster. + if miniConf.Kind == "EKSCluster" { + cOks, cErrs := tv.validateTools(kfdManifest.Tools.Eks) + oks = append(oks, cOks...) + errs = append(errs, cErrs...) } etv := apis.NewExtraToolsValidatorFactory(tv.executor, miniConf.APIVersion, miniConf.Kind, tv.autoConnect) @@ -100,3 +88,30 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( return oks, errs } + +func (tv *Validator) validateTools(i any) (oks []string, errs []error) { + toolCfgs := reflect.ValueOf(i) + for i := 0; i < toolCfgs.NumField(); i++ { + toolCfg, ok := toolCfgs.Field(i).Interface().(config.KFDTool) + if !ok { + continue + } + + toolName := strings.ToLower(toolCfgs.Type().Field(i).Name) + + tool, err := tv.toolFactory.Create(tool.ToolName(toolName), toolCfg.Version) + if err != nil { + errs = append(errs, err) + continue + } + + if err := tool.CheckBinVersion(); err != nil { + errs = append(errs, err) + continue + } + + oks = append(oks, toolName) + } + + return +} diff --git a/internal/dependencies/toolsconf/validator.go b/internal/dependencies/toolsconf/validator.go deleted file mode 100644 index 1f6c5b5f5..000000000 --- a/internal/dependencies/toolsconf/validator.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package toolsconf - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/sighupio/fury-distribution/pkg/apis/config" - "github.com/sighupio/furyctl/internal/tool/awscli" - execx "github.com/sighupio/furyctl/internal/x/exec" -) - -var ( - ErrAWSS3BucketNotFound = errors.New("AWS S3 Bucket not found, please create it before running furyctl") - ErrAWSS3BucketRegionMismatch = errors.New("AWS S3 Bucket region mismatch") -) - -func NewValidator(executor execx.Executor) *Validator { - return &Validator{ - awsCliRunner: awscli.NewRunner( - executor, - awscli.Paths{ - Awscli: "aws", - WorkDir: "", - }, - ), - } -} - -type Validator struct { - awsCliRunner *awscli.Runner -} - -func (v *Validator) Validate(s3Conf config.ToolsConfigurationTerrraformStateS3) ([]string, []error) { - return v.checkAWSS3Bucket(s3Conf.BucketName, s3Conf.Region) -} - -func (v *Validator) checkAWSS3Bucket(bucketName, region string) ([]string, []error) { - oks := make([]string, 0) - errs := make([]error, 0) - - r, err := v.awsCliRunner.S3Api("get-bucket-location", "--bucket", bucketName, "--output", "text") - if err != nil { - logrus.Debug(fmt.Errorf("error checking AWS S3 Bucket: %w", err)) - - errs = append(errs, ErrAWSS3BucketNotFound) - - return oks, errs - } - - // AWS S3 Bucket in us-east-1 region returns None as LocationConstraint - // https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-location.html#output - if r == "None" { - r = "us-east-1" - } - - if r != region { - errs = append(errs, fmt.Errorf("%w, expected %s, got %s", ErrAWSS3BucketRegionMismatch, region, r)) - - return oks, errs - } - - oks = append(oks, "AWS S3 Bucket") - - return oks, errs -} diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index 1606e7b4d..45f940809 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -10,7 +10,6 @@ import ( "github.com/sighupio/furyctl/internal/dependencies/envvars" "github.com/sighupio/furyctl/internal/dependencies/tools" - "github.com/sighupio/furyctl/internal/dependencies/toolsconf" "github.com/sighupio/furyctl/internal/distribution" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -25,14 +24,12 @@ func NewValidator(executor execx.Executor, binPath, furyctlPath string, autoConn return &Validator{ toolsValidator: tools.NewValidator(executor, binPath, furyctlPath, autoConnect), envVarsValidator: envvars.NewValidator(), - infraValidator: toolsconf.NewValidator(executor), } } type Validator struct { toolsValidator *tools.Validator envVarsValidator *envvars.Validator - infraValidator *toolsconf.Validator } func (v *Validator) ValidateBaseReqs() error { @@ -55,13 +52,5 @@ func (v *Validator) Validate(res distribution.DownloadResult) error { return fmt.Errorf("%w: %v", errValidatingEnv, errs) } - if res.MinimalConf.Spec.ToolsConfiguration != nil { - if _, errs := v.infraValidator.Validate( - res.MinimalConf.Spec.ToolsConfiguration.Terraform.State.S3, - ); len(errs) > 0 { - return fmt.Errorf("%w: %v", errValidatingToolsConf, errs) - } - } - return nil } diff --git a/internal/tool/runner.go b/internal/tool/runner.go index a8f894efd..0bb852d34 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -18,15 +18,17 @@ import ( execx "github.com/sighupio/furyctl/internal/x/exec" ) +type ToolName string + const ( - Ansible = "ansible" - Awscli = "awscli" - Furyagent = "furyagent" - Git = "git" - Kubectl = "kubectl" - Kustomize = "kustomize" - Openvpn = "openvpn" - Terraform = "terraform" + Ansible ToolName = "ansible" + Awscli ToolName = "awscli" + Furyagent ToolName = "furyagent" + Git ToolName = "git" + Kubectl ToolName = "kubectl" + Kustomize ToolName = "kustomize" + Openvpn ToolName = "openvpn" + Terraform ToolName = "terraform" ) type Runner interface { @@ -51,63 +53,61 @@ type RunnerFactory struct { paths RunnerFactoryPaths } -func (rf *RunnerFactory) Create(name, version, workDir string) Runner { - if name == Ansible { +func (rf *RunnerFactory) Create(name ToolName, version, workDir string) Runner { + switch name { + case Ansible: return ansible.NewRunner(rf.executor, ansible.Paths{ - Ansible: name, + Ansible: string(name), WorkDir: workDir, }) - } - if name == Awscli { + case Awscli: return awscli.NewRunner(rf.executor, awscli.Paths{ Awscli: "aws", WorkDir: workDir, }) - } - if name == Furyagent { + case Furyagent: return furyagent.NewRunner(rf.executor, furyagent.Paths{ - Furyagent: filepath.Join(rf.paths.Bin, name, version, name), + Furyagent: filepath.Join(rf.paths.Bin, string(name), version, string(name)), WorkDir: workDir, }) - } - if name == Git { + case Git: return git.NewRunner(rf.executor, git.Paths{ Git: "git", WorkDir: workDir, }) - } - - if name == Kubectl { - return kubectl.NewRunner(rf.executor, kubectl.Paths{ - Kubectl: filepath.Join(rf.paths.Bin, name, version, name), - WorkDir: workDir, - }, - false, true, true) - } - if name == Kustomize { + case Kubectl: + return kubectl.NewRunner( + rf.executor, + kubectl.Paths{ + Kubectl: filepath.Join(rf.paths.Bin, string(name), version, string(name)), + WorkDir: workDir, + }, + false, true, true, + ) + + case Kustomize: return kustomize.NewRunner(rf.executor, kustomize.Paths{ - Kustomize: filepath.Join(rf.paths.Bin, name, version, name), + Kustomize: filepath.Join(rf.paths.Bin, string(name), version, string(name)), WorkDir: workDir, }) - } - if name == Openvpn { + case Openvpn: return openvpn.NewRunner(rf.executor, openvpn.Paths{ - Openvpn: filepath.Join(rf.paths.Bin, name, version, name), + Openvpn: filepath.Join(rf.paths.Bin, string(name), version, string(name)), WorkDir: workDir, }) - } - if name == Terraform { + case Terraform: return terraform.NewRunner(rf.executor, terraform.Paths{ - Terraform: filepath.Join(rf.paths.Bin, name, version, name), + Terraform: filepath.Join(rf.paths.Bin, string(name), version, string(name)), WorkDir: workDir, }) - } - return nil + default: + return nil + } } From ba07070e51c358780672b7abf7aa109bffe9d1b5 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 13 Jun 2023 08:34:57 +0200 Subject: [PATCH 347/383] feat: add distro deleter and address linting issues --- .../distribution/create/distribution.go | 10 +- .../apis/kfd/v1alpha2/distribution/creator.go | 1 - .../distribution/delete/distribution.go | 332 ++++++++++++++++++ .../apis/kfd/v1alpha2/distribution/deleter.go | 91 +++++ .../apis/kfd/v1alpha2/distribution/init.go | 6 + internal/apis/kfd/v1alpha2/eks/tool.go | 17 +- internal/dependencies/download.go | 14 +- internal/dependencies/tools/tool.go | 2 +- internal/dependencies/tools/validator.go | 13 +- internal/dependencies/validate.go | 5 +- internal/tool/runner.go | 20 +- 11 files changed, 474 insertions(+), 37 deletions(-) create mode 100644 internal/apis/kfd/v1alpha2/distribution/delete/distribution.go create mode 100644 internal/apis/kfd/v1alpha2/distribution/deleter.go diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 169a680df..e58777730 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -44,7 +44,6 @@ type Distribution struct { kzRunner *kustomize.Runner kubeRunner *kubectl.Runner dryRun bool - phase string } func NewDistribution( @@ -52,7 +51,6 @@ func NewDistribution( furyctlConf public.KfddistributionKfdV1Alpha2, kfdManifest config.KFD, dryRun bool, - phase string, ) (*Distribution, error) { distroDir := path.Join(paths.WorkDir, cluster.OperationPhaseDistribution) @@ -85,7 +83,6 @@ func NewDistribution( false, ), dryRun: dryRun, - phase: phase, }, nil } @@ -179,10 +176,8 @@ func (d *Distribution) Exec() error { return errClusterConnect } - if d.phase == cluster.OperationPhaseDistribution { - logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + - "running the command without --dry-run") - } + logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + + "running the command without --dry-run") } logrus.Info("Checking if at least one storage class is available...") @@ -202,7 +197,6 @@ func (d *Distribution) Exec() error { "", "nodes", "--output", - //nolint:lll // string needed as is "jsonpath=\"{range .items[*]}{.spec.taints[?(@.key==\"node.kubernetes.io/not-ready\")]}{end}\"", ) if err != nil { diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go index e51a18f7a..00f2db768 100644 --- a/internal/apis/kfd/v1alpha2/distribution/creator.go +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -92,7 +92,6 @@ func (v *ClusterCreator) Create(_ string, _ int) error { v.furyctlConf, v.kfdManifest, v.dryRun, - v.phase, ) if err != nil { return fmt.Errorf("error while initiating distribution phase: %w", err) diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go new file mode 100644 index 000000000..e774cd6ba --- /dev/null +++ b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go @@ -0,0 +1,332 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package del + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/kubernetes" + "github.com/sighupio/furyctl/internal/tool/kustomize" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" +) + +const ( + ingressAfterDeleteDelay = 4 + checkPendingResourcesDelay = 20 + checkPendingResourcesMaxRetries = 5 +) + +var ( + errCheckPendingResources = errors.New("error while checking pending resources") + errPendingResources = errors.New("pending resources: ") + errClusterConnect = errors.New("error connecting to cluster") +) + +type Ingress struct { + Name string + Host []string +} + +type Distribution struct { + *cluster.OperationPhase + kzRunner *kustomize.Runner + kubeClient *kubernetes.Client + dryRun bool +} + +func NewDistribution( + dryRun bool, + workDir, + binPath string, + kfdManifest config.KFD, + kubeconfig string, +) (*Distribution, error) { + distroDir := path.Join(workDir, cluster.OperationPhaseDistribution) + + phase, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, binPath) + if err != nil { + return nil, fmt.Errorf("error creating distribution phase: %w", err) + } + + return &Distribution{ + OperationPhase: phase, + kzRunner: kustomize.NewRunner( + execx.NewStdExecutor(), + kustomize.Paths{ + Kustomize: phase.KustomizePath, + WorkDir: path.Join(phase.Path, "manifests"), + }, + ), + kubeClient: kubernetes.NewClient( + phase.KubectlPath, + path.Join(phase.Path, "manifests"), + kubeconfig, + true, + true, + false, + execx.NewStdExecutor(), + ), + dryRun: dryRun, + }, nil +} + +func (d *Distribution) Exec() error { + logrus.Info("Deleting Kubernetes Fury Distribution...") + + if err := iox.CheckDirIsEmpty(d.OperationPhase.Path); err == nil { + logrus.Info("Kubernetes Fury Distribution already deleted, skipping...") + + logrus.Debug("Distribution phase already executed, skipping...") + + return nil + } + + logrus.Info("Checking cluster connectivity...") + + if _, err := d.kubeClient.ToolVersion(); err != nil { + return errClusterConnect + } + + if d.dryRun { + manifestsOutPath, err := d.buildManifests() + if err != nil { + return err + } + + if _, err = d.kubeClient.DeleteFromPath(manifestsOutPath, "--dry-run=client"); err != nil { + logrus.Errorf("error while deleting resources: %v", err) + } + + logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") + + if _, err := d.kubeClient.ListNamespaceResources("ingress", "all"); err != nil { + logrus.Errorf("error while getting list of ingress resources: %v", err) + } + + if _, err := d.kubeClient.ListNamespaceResources("prometheus", "monitoring"); err != nil { + logrus.Errorf("error while getting list of prometheus resources: %v", err) + } + + if _, err := d.kubeClient.ListNamespaceResources("persistentvolumeclaim", "monitoring"); err != nil { + logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) + } + + if _, err := d.kubeClient.ListNamespaceResources("persistentvolumeclaim", "logging"); err != nil { + logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) + } + + if _, err := d.kubeClient.ListNamespaceResources("statefulset", "logging"); err != nil { + logrus.Errorf("error while getting list of statefulset resources: %v", err) + } + + if _, err := d.kubeClient.ListNamespaceResources("logging", "logging"); err != nil { + logrus.Errorf("error while getting list of logging resources: %v", err) + } + + if _, err := d.kubeClient.ListNamespaceResources("service", "ingress-nginx"); err != nil { + logrus.Errorf("error while getting list of service resources: %v", err) + } + + return nil + } + + logrus.Info("Deleting ingresses...") + + if err := d.deleteIngresses(); err != nil { + return err + } + + logrus.Warn("Deleting blocking resources, this operation will take a few minutes!") + + if err := d.deleteBlockingResources(); err != nil { + return err + } + + logrus.Info("Building manifests...") + + manifestsOutPath, err := d.buildManifests() + if err != nil { + return err + } + + logrus.Info("Deleting kubernetes resources...") + + _, err = d.kubeClient.DeleteFromPath(manifestsOutPath) + if err != nil { + logrus.Errorf("error while deleting resources: %v", err) + } + + logrus.Info("Checking pending resources...") + + return d.checkPendingResources() +} + +func (d *Distribution) buildManifests() (string, error) { + kzOut, err := d.kzRunner.Build() + if err != nil { + return "", fmt.Errorf("error building manifests: %w", err) + } + + outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") + if err != nil { + return "", fmt.Errorf("error creating temp dir: %w", err) + } + + manifestsOutPath := filepath.Join(outDirPath, "out.yaml") + + logrus.Debugf("built manifests = %s", manifestsOutPath) + + if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { + return "", fmt.Errorf("error writing built manifests: %w", err) + } + + return manifestsOutPath, nil +} + +func (d *Distribution) checkPendingResources() error { + var errSvc, errPv, errIgrs error + + var ingrs []kubernetes.Ingress + + var lbs, pvs []string + + dur := time.Second * checkPendingResourcesDelay + + maxRetries := checkPendingResourcesMaxRetries + + retries := 0 + + for retries < maxRetries { + p := time.NewTicker(dur) + + if <-p.C; true { + lbs, errSvc = d.kubeClient.GetLoadBalancers() + if errSvc == nil && len(lbs) > 0 { + errSvc = fmt.Errorf("%w: %v", errPendingResources, lbs) + } + + pvs, errPv = d.kubeClient.GetPersistentVolumes() + if errPv == nil && len(pvs) > 0 { + errPv = fmt.Errorf("%w: %v", errPendingResources, pvs) + } + + ingrs, errIgrs = d.kubeClient.GetIngresses() + if errIgrs == nil && len(ingrs) > 0 { + errIgrs = fmt.Errorf("%w: %v", errPendingResources, ingrs) + } + + if errSvc == nil && errPv == nil && errIgrs == nil { + return nil + } + } + + retries++ + + p.Stop() + } + + return fmt.Errorf("%w:\n%v\n%v\n%v", errCheckPendingResources, errSvc, errPv, errIgrs) +} + +func (d *Distribution) deleteIngresses() error { + _, err := d.kubeClient.DeleteResourcesInAllNamespaces("ingress") + if err != nil { + return fmt.Errorf("error deleting ingresses: %w", err) + } + + return nil +} + +func (d *Distribution) deleteBlockingResources() error { + if err := d.deleteResource("deployment", "logging", "loki-distributed-distributor"); err != nil { + return err + } + + if err := d.deleteResource("deployment", "logging", "loki-distributed-compactor"); err != nil { + return err + } + + if err := d.deleteResources("prometheuses.monitoring.coreos.com", "monitoring"); err != nil { + return err + } + + if err := d.deleteResources("prometheusrules.monitoring.coreos.com", "monitoring"); err != nil { + return err + } + + if err := d.deleteResources("persistentvolumeclaims", "monitoring"); err != nil { + return err + } + + if err := d.deleteResources("loggings.logging.banzaicloud.io", "logging"); err != nil { + return err + } + + if err := d.deleteResources("statefulsets.apps", "logging"); err != nil { + return err + } + + if err := d.deleteResources("persistentvolumeclaims", "logging"); err != nil { + return err + } + + if err := d.deleteResources("services", "ingress-nginx"); err != nil { + return err + } + + logrus.Debugf("waiting for resources to be deleted...") + + time.Sleep(time.Minute * ingressAfterDeleteDelay) + + return nil +} + +func (d *Distribution) deleteResource(typ, ns, name string) error { + logrus.Infof("Deleting %ss '%s' in namespace '%s'...\n", typ, name, ns) + + resExists, err := d.kubeClient.ResourceExists(name, typ, ns) + if err != nil { + return fmt.Errorf("error checking if %s '%s' exists in '%s' namespace: %w", typ, name, ns, err) + } + + if resExists { + _, err = d.kubeClient.DeleteResource(name, typ, ns) + if err != nil { + return fmt.Errorf("error deleting %s '%s' in '%s' namespace: %w", typ, name, ns, err) + } + } + + return nil +} + +func (d *Distribution) deleteResources(typ, ns string) error { + logrus.Infof("Deleting %ss in namespace '%s'...\n", typ, ns) + + hasResTyp, err := d.kubeClient.HasResourceType(typ) + if err != nil { + return fmt.Errorf("error checking '%s' resources type: %w", typ, err) + } + + if !hasResTyp { + return nil + } + + _, err = d.kubeClient.DeleteResources(typ, ns) + if err != nil { + return fmt.Errorf("error deleting '%s' in namespace '%s': %w", typ, ns, err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/distribution/deleter.go b/internal/apis/kfd/v1alpha2/distribution/deleter.go new file mode 100644 index 000000000..d708f2b0a --- /dev/null +++ b/internal/apis/kfd/v1alpha2/distribution/deleter.go @@ -0,0 +1,91 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package distribution + +import ( + "fmt" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/sighupio/fury-distribution/pkg/apis/config" + "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" + del "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/distribution/delete" + "github.com/sighupio/furyctl/internal/cluster" +) + +type ClusterDeleter struct { + paths cluster.DeleterPaths + kfdManifest config.KFD + furyctlConf public.KfddistributionKfdV1Alpha2 + phase string + dryRun bool +} + +func (d *ClusterDeleter) SetProperties(props []cluster.DeleterProperty) { + for _, prop := range props { + d.SetProperty(prop.Name, prop.Value) + } +} + +func (d *ClusterDeleter) SetProperty(name string, value any) { + lcName := strings.ToLower(name) + + switch lcName { + case cluster.DeleterPropertyKfdManifest: + if kfdManifest, ok := value.(config.KFD); ok { + d.kfdManifest = kfdManifest + } + + case cluster.DeleterPropertyFuryctlConf: + if s, ok := value.(public.KfddistributionKfdV1Alpha2); ok { + d.furyctlConf = s + } + + case cluster.DeleterPropertyPhase: + if s, ok := value.(string); ok { + d.phase = s + } + + case cluster.DeleterPropertyWorkDir: + if s, ok := value.(string); ok { + d.paths.WorkDir = s + } + + case cluster.DeleterPropertyBinPath: + if s, ok := value.(string); ok { + d.paths.BinPath = s + } + + case cluster.DeleterPropertyKubeconfig: + if s, ok := value.(string); ok { + d.paths.Kubeconfig = s + } + + case cluster.DeleterPropertyDryRun: + if b, ok := value.(bool); ok { + d.dryRun = b + } + } +} + +func (d *ClusterDeleter) Delete() error { + if d.phase != "" && d.phase != cluster.OperationPhaseDistribution { + return ErrUnsupportedPhase + } + + distro, err := del.NewDistribution(d.dryRun, d.paths.WorkDir, d.paths.BinPath, d.kfdManifest, d.paths.Kubeconfig) + if err != nil { + return fmt.Errorf("error while initiating distribution phase: %w", err) + } + + if err := distro.Exec(); err != nil { + return fmt.Errorf("error while deleting Kubernetes Fury Distribution: %w", err) + } + + logrus.Info("Kubernetes Fury Distribution deleted successfully") + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/distribution/init.go b/internal/apis/kfd/v1alpha2/distribution/init.go index 769d06ede..32aab68f5 100644 --- a/internal/apis/kfd/v1alpha2/distribution/init.go +++ b/internal/apis/kfd/v1alpha2/distribution/init.go @@ -16,4 +16,10 @@ func init() { "KFDDistribution", cluster.NewCreatorFactory[*ClusterCreator, public.KfddistributionKfdV1Alpha2](&ClusterCreator{}), ) + + cluster.RegisterDeleterFactory( + "kfd.sighup.io/v1alpha2", + "KFDDistribution", + cluster.NewDeleterFactory[*ClusterDeleter, public.KfddistributionKfdV1Alpha2](&ClusterDeleter{}), + ) } diff --git a/internal/apis/kfd/v1alpha2/eks/tool.go b/internal/apis/kfd/v1alpha2/eks/tool.go index 30e169525..2c7339566 100644 --- a/internal/apis/kfd/v1alpha2/eks/tool.go +++ b/internal/apis/kfd/v1alpha2/eks/tool.go @@ -87,19 +87,30 @@ func (x *ExtraToolsValidator) terraformStateAWSS3Bucket(conf private.EksclusterK }, ) - r, err := awsCliRunner.S3Api("get-bucket-location", "--bucket", string(conf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName), "--output", "text") + r, err := awsCliRunner.S3Api( + "get-bucket-location", + "--bucket", + string(conf.Spec.ToolsConfiguration.Terraform.State.S3.BucketName), + "--output", + "text", + ) if err != nil { return ErrAWSS3BucketNotFound } // AWS S3 Bucket in us-east-1 region returns None as LocationConstraint - // https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-location.html#output + //nolint:lll // https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-location.html#output if r == "None" { r = "us-east-1" } if r != string(conf.Spec.ToolsConfiguration.Terraform.State.S3.Region) { - return fmt.Errorf("%w, expected %s, got %s", ErrAWSS3BucketRegionMismatch, conf.Spec.ToolsConfiguration.Terraform.State.S3.Region, r) + return fmt.Errorf( + "%w, expected %s, got %s", + ErrAWSS3BucketRegionMismatch, + conf.Spec.ToolsConfiguration.Terraform.State.S3.Region, + r, + ) } return nil diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index cfb05d9e3..9aaef8269 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -228,12 +228,12 @@ func (dd *Downloader) DownloadTools(kfdTools config.KFDTools) ([]string, error) return unsupportedTools, fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) } - tool, err := dd.toolFactory.Create(tool.ToolName(name), toolCfg.Version) + tfc, err := dd.toolFactory.Create(tool.Name(name), toolCfg.Version) if err != nil { return unsupportedTools, fmt.Errorf("%s: %w", name, err) } - if tool == nil || !tool.SupportsDownload() { + if tfc == nil || !tfc.SupportsDownload() { unsupportedTools = append(unsupportedTools, name) continue @@ -241,16 +241,16 @@ func (dd *Downloader) DownloadTools(kfdTools config.KFDTools) ([]string, error) dst := filepath.Join(dd.binPath, name, toolCfg.Version) - if err := dd.client.Download(tool.SrcPath(), dst); err != nil { - return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, tool.SrcPath(), err) + if err := dd.client.Download(tfc.SrcPath(), dst); err != nil { + return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrDownloadingFolder, tfc.SrcPath(), err) } - if err := tool.Rename(dst); err != nil { - return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrRenamingFile, tool.SrcPath(), err) + if err := tfc.Rename(dst); err != nil { + return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrRenamingFile, tfc.SrcPath(), err) } if err := os.Chmod(filepath.Join(dst, name), iox.FullPermAccess); err != nil { - return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrChangingFilePermissions, tool.SrcPath(), err) + return unsupportedTools, fmt.Errorf("%w '%s': %v", distribution.ErrChangingFilePermissions, tfc.SrcPath(), err) } } } diff --git a/internal/dependencies/tools/tool.go b/internal/dependencies/tools/tool.go index 1c68130dd..26af0acdd 100644 --- a/internal/dependencies/tools/tool.go +++ b/internal/dependencies/tools/tool.go @@ -61,7 +61,7 @@ type Factory struct { runnerFactory *tool.RunnerFactory } -func (f *Factory) Create(name tool.ToolName, version string) (Tool, error) { +func (f *Factory) Create(name tool.Name, version string) (Tool, error) { t := f.runnerFactory.Create(name, version, "") if name == tool.Ansible { diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index 1d75ef7d0..6fc70ecee 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -11,7 +11,6 @@ import ( "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/apis" - "github.com/sighupio/furyctl/internal/tool" itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -89,7 +88,11 @@ func (tv *Validator) Validate(kfdManifest config.KFD, miniConf config.Furyctl) ( return oks, errs } -func (tv *Validator) validateTools(i any) (oks []string, errs []error) { +func (tv *Validator) validateTools(i any) ([]string, []error) { + var oks []string + + var errs []error + toolCfgs := reflect.ValueOf(i) for i := 0; i < toolCfgs.NumField(); i++ { toolCfg, ok := toolCfgs.Field(i).Interface().(config.KFDTool) @@ -99,19 +102,21 @@ func (tv *Validator) validateTools(i any) (oks []string, errs []error) { toolName := strings.ToLower(toolCfgs.Type().Field(i).Name) - tool, err := tv.toolFactory.Create(tool.ToolName(toolName), toolCfg.Version) + tool, err := tv.toolFactory.Create(itool.Name(toolName), toolCfg.Version) if err != nil { errs = append(errs, err) + continue } if err := tool.CheckBinVersion(); err != nil { errs = append(errs, err) + continue } oks = append(oks, toolName) } - return + return oks, errs } diff --git a/internal/dependencies/validate.go b/internal/dependencies/validate.go index 45f940809..3f1bf08c2 100644 --- a/internal/dependencies/validate.go +++ b/internal/dependencies/validate.go @@ -15,9 +15,8 @@ import ( ) var ( - errValidatingTools = errors.New("errors validating tools") - errValidatingEnv = errors.New("errors validating env vars") - errValidatingToolsConf = errors.New("errors validating tools configuration") + errValidatingTools = errors.New("errors validating tools") + errValidatingEnv = errors.New("errors validating env vars") ) func NewValidator(executor execx.Executor, binPath, furyctlPath string, autoConnect bool) *Validator { diff --git a/internal/tool/runner.go b/internal/tool/runner.go index 0bb852d34..9dc88868c 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -18,17 +18,17 @@ import ( execx "github.com/sighupio/furyctl/internal/x/exec" ) -type ToolName string +type Name string const ( - Ansible ToolName = "ansible" - Awscli ToolName = "awscli" - Furyagent ToolName = "furyagent" - Git ToolName = "git" - Kubectl ToolName = "kubectl" - Kustomize ToolName = "kustomize" - Openvpn ToolName = "openvpn" - Terraform ToolName = "terraform" + Ansible Name = "ansible" + Awscli Name = "awscli" + Furyagent Name = "furyagent" + Git Name = "git" + Kubectl Name = "kubectl" + Kustomize Name = "kustomize" + Openvpn Name = "openvpn" + Terraform Name = "terraform" ) type Runner interface { @@ -53,7 +53,7 @@ type RunnerFactory struct { paths RunnerFactoryPaths } -func (rf *RunnerFactory) Create(name ToolName, version, workDir string) Runner { +func (rf *RunnerFactory) Create(name Name, version, workDir string) Runner { switch name { case Ansible: return ansible.NewRunner(rf.executor, ansible.Paths{ From b6770f27908314203fcd6ac6a344c69604941148 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 13 Jun 2023 09:10:07 +0200 Subject: [PATCH 348/383] fix: fix tests --- internal/dependencies/download.go | 6 +- internal/dependencies/tools/awscli.go | 2 +- internal/dependencies/tools/furyagent_test.go | 9 ++- internal/dependencies/tools/kubectl_test.go | 6 +- internal/dependencies/tools/terraform_test.go | 3 +- internal/dependencies/tools/tool.go | 21 +++--- internal/dependencies/tools/tool_test.go | 3 +- internal/dependencies/tools/validator.go | 14 +--- internal/dependencies/tools/validator_test.go | 73 +++++-------------- internal/tool/runner_test.go | 3 +- 10 files changed, 52 insertions(+), 88 deletions(-) diff --git a/internal/dependencies/download.go b/internal/dependencies/download.go index 9aaef8269..b0420f230 100644 --- a/internal/dependencies/download.go +++ b/internal/dependencies/download.go @@ -228,11 +228,7 @@ func (dd *Downloader) DownloadTools(kfdTools config.KFDTools) ([]string, error) return unsupportedTools, fmt.Errorf("%s: %w", name, ErrModuleHasNoVersion) } - tfc, err := dd.toolFactory.Create(tool.Name(name), toolCfg.Version) - if err != nil { - return unsupportedTools, fmt.Errorf("%s: %w", name, err) - } - + tfc := dd.toolFactory.Create(tool.Name(name), toolCfg.Version) if tfc == nil || !tfc.SupportsDownload() { unsupportedTools = append(unsupportedTools, name) diff --git a/internal/dependencies/tools/awscli.go b/internal/dependencies/tools/awscli.go index 5af6163ec..9b3c66c14 100644 --- a/internal/dependencies/tools/awscli.go +++ b/internal/dependencies/tools/awscli.go @@ -54,7 +54,7 @@ func (*Awscli) Rename(_ string) error { func (a *Awscli) CheckBinVersion() error { if err := a.checker.version(a.version); err != nil { - return fmt.Errorf("aws-cli: %w", err) + return fmt.Errorf("awscli: %w", err) } return nil diff --git a/internal/dependencies/tools/furyagent_test.go b/internal/dependencies/tools/furyagent_test.go index 94d5e8121..e76f7157a 100644 --- a/internal/dependencies/tools/furyagent_test.go +++ b/internal/dependencies/tools/furyagent_test.go @@ -29,8 +29,9 @@ func Test_FuryAgent_SupportsDownload(t *testing.T) { func Test_FuryAgent_SrcPath(t *testing.T) { wantSrcPath := fmt.Sprintf( - "https://github.com/sighupio/furyagent/releases/download/v0.3.0/furyagent-%s-amd64", + "https://github.com/sighupio/furyagent/releases/download/v0.3.0/furyagent-%s-%s", runtime.GOOS, + runtime.GOARCH, ) testCases := []struct { @@ -62,7 +63,11 @@ func Test_FuryAgent_Rename(t *testing.T) { t.Fatalf("error creating temp dir: %v", err) } - if _, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf("furyagent-%s-amd64", runtime.GOOS))); err != nil { + if _, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf( + "furyagent-%s-%s", + runtime.GOOS, + runtime.GOARCH, + ))); err != nil { t.Fatalf("error creating temp file: %v", err) } diff --git a/internal/dependencies/tools/kubectl_test.go b/internal/dependencies/tools/kubectl_test.go index 9eb3fc554..ca5f81e6c 100644 --- a/internal/dependencies/tools/kubectl_test.go +++ b/internal/dependencies/tools/kubectl_test.go @@ -28,7 +28,11 @@ func Test_Kubectl_SupportsDownload(t *testing.T) { } func Test_Kubectl_SrcPath(t *testing.T) { - wantSrcPath := fmt.Sprintf("https://dl.k8s.io/release/v1.24.9/bin/%s/amd64/kubectl", runtime.GOOS) + wantSrcPath := fmt.Sprintf( + "https://dl.k8s.io/release/v1.24.9/bin/%s/%s/kubectl", + runtime.GOOS, + runtime.GOARCH, + ) testCases := []struct { desc string diff --git a/internal/dependencies/tools/terraform_test.go b/internal/dependencies/tools/terraform_test.go index e36475c21..b0ef88dec 100644 --- a/internal/dependencies/tools/terraform_test.go +++ b/internal/dependencies/tools/terraform_test.go @@ -29,8 +29,9 @@ func Test_Terraform_SupportsDownload(t *testing.T) { func Test_Terraform_SrcPath(t *testing.T) { wantSrcPath := fmt.Sprintf( - "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_%s_amd64.zip", + "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_%s_%s.zip", runtime.GOOS, + runtime.GOARCH, ) testCases := []struct { diff --git a/internal/dependencies/tools/tool.go b/internal/dependencies/tools/tool.go index 26af0acdd..8b8abe246 100644 --- a/internal/dependencies/tools/tool.go +++ b/internal/dependencies/tools/tool.go @@ -31,7 +31,6 @@ var ( errCannotParse = errors.New("can't parse system tool version") errMissingBin = errors.New("missing binary from vendor folder") errGetVersion = errors.New("can't get tool version") - errUnknowToolName = errors.New("unknow tool name") ) type Tool interface { @@ -61,7 +60,7 @@ type Factory struct { runnerFactory *tool.RunnerFactory } -func (f *Factory) Create(name tool.Name, version string) (Tool, error) { +func (f *Factory) Create(name tool.Name, version string) Tool { t := f.runnerFactory.Create(name, version, "") if name == tool.Ansible { @@ -70,7 +69,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected ansible.Runner, got %T", t)) } - return NewAnsible(a, version), nil + return NewAnsible(a, version) } if name == tool.Awscli { @@ -79,7 +78,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected awscli.Runner, got %T", t)) } - return NewAwscli(a, version), nil + return NewAwscli(a, version) } if name == tool.Furyagent { @@ -88,7 +87,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected furyagent.Runner, got %T", t)) } - return NewFuryagent(fa, version), nil + return NewFuryagent(fa, version) } if name == tool.Git { @@ -97,7 +96,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected git.Runner, got %T", t)) } - return NewGit(g, version), nil + return NewGit(g, version) } if name == tool.Kubectl { @@ -106,7 +105,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected kubectl.Runner, got %T", t)) } - return NewKubectl(k, version), nil + return NewKubectl(k, version) } if name == tool.Kustomize { @@ -115,7 +114,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected kustomize.Runner, got %T", t)) } - return NewKustomize(k, version), nil + return NewKustomize(k, version) } if name == tool.Openvpn { @@ -124,7 +123,7 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected openvpn.Runner, got %T", t)) } - return NewOpenvpn(o, version), nil + return NewOpenvpn(o, version) } if name == tool.Terraform { @@ -133,10 +132,10 @@ func (f *Factory) Create(name tool.Name, version string) (Tool, error) { panic(fmt.Sprintf("expected terraform.Runner, got %T", t)) } - return NewTerraform(tf, version), nil + return NewTerraform(tf, version) } - return nil, errUnknowToolName + return nil } type checker struct { diff --git a/internal/dependencies/tools/tool_test.go b/internal/dependencies/tools/tool_test.go index 386c5160b..a1865e5e3 100644 --- a/internal/dependencies/tools/tool_test.go +++ b/internal/dependencies/tools/tool_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/sighupio/furyctl/internal/dependencies/tools" + itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -50,7 +51,7 @@ func Test_Factory_Create(t *testing.T) { Bin: "", }) t.Run(tC.desc, func(t *testing.T) { - tool := f.Create(tC.desc, "0.0.0") + tool := f.Create(itool.Name(tC.desc), "0.0.0") if tool == nil && tC.wantTool { t.Errorf("Expected tool %s, got nil", tC.desc) } diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index 6fc70ecee..3480ef04b 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -41,11 +41,7 @@ func (tv *Validator) ValidateBaseReqs() ([]string, []error) { errs []error ) - git, err := tv.toolFactory.Create(itool.Git, "*") - if err != nil { - errs = append(errs, err) - } - + git := tv.toolFactory.Create(itool.Git, "*") if err := git.CheckBinVersion(); err != nil { errs = append(errs, err) } else { @@ -102,13 +98,7 @@ func (tv *Validator) validateTools(i any) ([]string, []error) { toolName := strings.ToLower(toolCfgs.Type().Field(i).Name) - tool, err := tv.toolFactory.Create(itool.Name(toolName), toolCfg.Version) - if err != nil { - errs = append(errs, err) - - continue - } - + tool := tv.toolFactory.Create(itool.Name(toolName), toolCfg.Version) if err := tool.CheckBinVersion(); err != nil { errs = append(errs, err) diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 2cf7bb6ce..377b3e97e 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -29,59 +29,38 @@ func Test_Validator_Validate(t *testing.T) { desc: "all tools are installed in their correct version", manifest: config.KFD{ Tools: config.KFDTools{ - Common: config.Common{ - Kubectl: config.Tool{Version: "1.21.1"}, - Kustomize: config.Tool{Version: "3.9.4"}, - Terraform: config.Tool{Version: "0.15.4"}, - Furyagent: config.Tool{Version: "0.3.0"}, + Common: config.KFDToolsCommon{ + Kubectl: config.KFDTool{Version: "1.21.1"}, + Kustomize: config.KFDTool{Version: "3.9.4"}, + Terraform: config.KFDTool{Version: "0.15.4"}, + Furyagent: config.KFDTool{Version: "0.3.0"}, }, }, }, state: config.Furyctl{ - Spec: config.FuryctlSpec{ - ToolsConfiguration: &config.ToolsConfiguration{ - Terraform: config.ToolsConfigurationTerraform{ - State: config.ToolsConfigurationTerrraformState{ - S3: config.ToolsConfigurationTerrraformStateS3{ - BucketName: "test", - }, - }, - }, - }, - }, + Spec: config.FuryctlSpec{}, }, wantOks: []string{ "kubectl", "kustomize", "terraform", "furyagent", - "aws", }, }, { desc: "all tools are installed in their wrong version", manifest: config.KFD{ Tools: config.KFDTools{ - Common: config.Common{ - Kubectl: config.Tool{Version: "1.22.0"}, - Kustomize: config.Tool{Version: "3.5.3"}, - Terraform: config.Tool{Version: "1.3.0"}, - Furyagent: config.Tool{Version: "0.4.0"}, + Common: config.KFDToolsCommon{ + Kubectl: config.KFDTool{Version: "1.22.0"}, + Kustomize: config.KFDTool{Version: "3.5.3"}, + Terraform: config.KFDTool{Version: "1.3.0"}, + Furyagent: config.KFDTool{Version: "0.4.0"}, }, }, }, state: config.Furyctl{ - Spec: config.FuryctlSpec{ - ToolsConfiguration: &config.ToolsConfiguration{ - Terraform: config.ToolsConfigurationTerraform{ - State: config.ToolsConfigurationTerrraformState{ - S3: config.ToolsConfigurationTerrraformStateS3{ - BucketName: "test", - }, - }, - }, - }, - }, + Spec: config.FuryctlSpec{}, }, wantErrs: []error{ errors.New("furyagent: wrong tool version - installed = 0.3.0, expected = 0.4.0"), @@ -89,42 +68,30 @@ func Test_Validator_Validate(t *testing.T) { errors.New("kustomize: wrong tool version - installed = 3.9.4, expected = 3.5.3"), errors.New("terraform: wrong tool version - installed = 0.15.4, expected = 1.3.0"), }, - wantOks: []string{"aws"}, }, { - desc: "openvpn is installed", + desc: "all tools for EKSCluster kind are installed", manifest: config.KFD{ Tools: config.KFDTools{ - Common: config.Common{ - Kubectl: config.Tool{Version: "1.21.1"}, - Kustomize: config.Tool{Version: "3.9.4"}, - Terraform: config.Tool{Version: "0.15.4"}, - Furyagent: config.Tool{Version: "0.3.0"}, + Common: config.KFDToolsCommon{ + Kubectl: config.KFDTool{Version: "1.21.1"}, + Kustomize: config.KFDTool{Version: "3.9.4"}, + Terraform: config.KFDTool{Version: "0.15.4"}, + Furyagent: config.KFDTool{Version: "0.3.0"}, }, }, }, state: config.Furyctl{ APIVersion: "kfd.sighup.io/v1alpha2", Kind: "EKSCluster", - Spec: config.FuryctlSpec{ - ToolsConfiguration: &config.ToolsConfiguration{ - Terraform: config.ToolsConfigurationTerraform{ - State: config.ToolsConfigurationTerrraformState{ - S3: config.ToolsConfigurationTerrraformStateS3{ - BucketName: "test", - }, - }, - }, - }, - }, + Spec: config.FuryctlSpec{}, }, wantOks: []string{ "kubectl", "kustomize", "terraform", "furyagent", - "aws", - "openvpn", + "awscli", }, }, } diff --git a/internal/tool/runner_test.go b/internal/tool/runner_test.go index 49cc7c05a..e1e9543d4 100644 --- a/internal/tool/runner_test.go +++ b/internal/tool/runner_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/sighupio/furyctl/internal/tool" + itool "github.com/sighupio/furyctl/internal/tool" execx "github.com/sighupio/furyctl/internal/x/exec" ) @@ -76,7 +77,7 @@ func Test_RunnerFactory_Create(t *testing.T) { Bin: os.TempDir(), }) - runner := rf.Create(tC.tool, "", os.TempDir()) + runner := rf.Create(itool.Name(tC.tool), "", os.TempDir()) if tC.wantRunner && runner == nil { t.Errorf("expected a runner, got nil") From c26962a63e9e2362e39bd70ee351e69f6894b16d Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 13 Jun 2023 10:17:03 +0200 Subject: [PATCH 349/383] fix: fix tests for ekscluster --- internal/dependencies/tools/tool_test.go | 2 ++ internal/dependencies/tools/validator_test.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/internal/dependencies/tools/tool_test.go b/internal/dependencies/tools/tool_test.go index a1865e5e3..a850fff1d 100644 --- a/internal/dependencies/tools/tool_test.go +++ b/internal/dependencies/tools/tool_test.go @@ -89,6 +89,8 @@ func TestHelperProcess(t *testing.T) { switch subcmd { case "--version": fmt.Fprintf(os.Stdout, "aws-cli/2.8.12 Python/3.11.0 Darwin/21.6.0 source/arm64 prompt/off\n") + case "s3api": + fmt.Fprintf(os.Stdout, "eu-west-1\n") } case "furyagent": switch subcmd { diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 377b3e97e..3ea3411fc 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -79,6 +79,9 @@ func Test_Validator_Validate(t *testing.T) { Terraform: config.KFDTool{Version: "0.15.4"}, Furyagent: config.KFDTool{Version: "0.3.0"}, }, + Eks: config.KFDToolsEks{ + Awscli: config.KFDTool{Version: "2.8.12"}, + }, }, }, state: config.Furyctl{ @@ -92,6 +95,8 @@ func Test_Validator_Validate(t *testing.T) { "terraform", "furyagent", "awscli", + "openvpn", + "terraform state aws s3 bucket", }, }, } From a390cbcd255698aa930e18e72fc07788ace66056 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 15 Jun 2023 08:50:10 +0200 Subject: [PATCH 350/383] feat: remove default ekscluster on create config --- cmd/create/config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/create/config.go b/cmd/create/config.go index 8b43e6bca..7e926a15f 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -68,6 +68,9 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { if err != nil { return fmt.Errorf("%w: kind", ErrParsingFlag) } + if kind == "" { + return fmt.Errorf("%w: kind", ErrMandatoryFlag) + } apiVersion, err := cmdutil.StringFlag(cmd, "api-version", tracker, cmdEvent) if err != nil { @@ -191,7 +194,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { cmd.Flags().StringP( "kind", "k", - "EKSCluster", + "", "Type of cluster to create (eg: EKSCluster, KFDDistribution)", ) From 09ef393973add37421ca9ce24a31dafc3f3f72ed Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 15 Jun 2023 08:53:12 +0200 Subject: [PATCH 351/383] feat: add missing storeClusterConfig --- .../apis/kfd/v1alpha2/distribution/creator.go | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go index 00f2db768..5332168ec 100644 --- a/internal/apis/kfd/v1alpha2/distribution/creator.go +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -7,6 +7,8 @@ package distribution import ( "errors" "fmt" + "os" + "path" "strings" "github.com/sirupsen/logrus" @@ -15,6 +17,10 @@ import ( "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" "github.com/sighupio/furyctl/internal/apis/kfd/v1alpha2/distribution/create" "github.com/sighupio/furyctl/internal/cluster" + "github.com/sighupio/furyctl/internal/tool/kubectl" + execx "github.com/sighupio/furyctl/internal/x/exec" + iox "github.com/sighupio/furyctl/internal/x/io" + kubex "github.com/sighupio/furyctl/internal/x/kube" ) var ErrUnsupportedPhase = errors.New("unsupported phase") @@ -101,6 +107,12 @@ func (v *ClusterCreator) Create(_ string, _ int) error { return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) } + if !v.dryRun { + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + } + if v.dryRun { logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") @@ -111,3 +123,37 @@ func (v *ClusterCreator) Create(_ string, _ int) error { return nil } + +func (v *ClusterCreator) storeClusterConfig() error { + x, err := os.ReadFile(v.paths.ConfigPath) + if err != nil { + return fmt.Errorf("error while reading config file: %w", err) + } + + secret, err := kubex.CreateSecret(x, "furyctl-config", "kube-system") + if err != nil { + return fmt.Errorf("error while creating secret: %w", err) + } + + secretPath := path.Join(v.paths.WorkDir, "secrets.yaml") + + if err := iox.WriteFile(secretPath, secret); err != nil { + return fmt.Errorf("error while writing secret: %w", err) + } + + defer os.Remove(secretPath) + + runner := kubectl.NewRunner(execx.NewStdExecutor(), kubectl.Paths{ + Kubectl: path.Join(v.paths.BinPath, "kubectl", v.kfdManifest.Tools.Common.Kubectl.Version, "kubectl"), + WorkDir: v.paths.WorkDir, + Kubeconfig: v.paths.Kubeconfig, + }, true, true, false) + + logrus.Info("Saving furyctl configuration file in the cluster...") + + if err := runner.Apply(secretPath); err != nil { + return fmt.Errorf("error while saving furyctl configuration file in the cluster: %w", err) + } + + return nil +} From c7827324e6159e7dc9ae1099265fb6b967aba874 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 15 Jun 2023 15:38:58 +0200 Subject: [PATCH 352/383] fix: fix e2e test --- test/e2e/furyctl_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/furyctl_test.go b/test/e2e/furyctl_test.go index dc8ef5493..4a311c423 100644 --- a/test/e2e/furyctl_test.go +++ b/test/e2e/furyctl_test.go @@ -454,6 +454,7 @@ var ( return RunCmd( furyctl, "create", "config", "--config", workdir+"/target/furyctl.yaml", + "--kind", "EKSCluster", "--debug", "--disable-analytics", "true", "--distro-location", absBasepath+"/distro", From 3115999d7ac8277627b003690f10cf8435f7c9c4 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 27 Jun 2023 14:36:44 +0200 Subject: [PATCH 353/383] mod: use correct dependency --- go.sum | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/go.sum b/go.sum index 2c7cf25bd..b2bf31333 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= github.com/briandowns/spinner v1.19.0/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -75,6 +76,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -88,6 +91,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -164,6 +168,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -174,6 +179,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= From 5d2459fd0eaf624d7350878804b4f7bbd7b352d7 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 19 Jun 2023 14:02:45 +0200 Subject: [PATCH 354/383] feat: add yq and bash --- .../distribution/create/distribution.go | 6 + internal/cluster/phase.go | 3 + internal/dependencies/tools/bash.go | 59 ++++++++ internal/dependencies/tools/bash_test.go | 75 ++++++++++ internal/dependencies/tools/git.go | 2 +- .../dependencies/tools/test_data/yq/4.33.0/yq | 5 + .../dependencies/tools/test_data/yq/4.34.1/yq | 5 + internal/dependencies/tools/tool.go | 47 +++--- internal/dependencies/tools/tool_test.go | 18 +++ internal/dependencies/tools/validator.go | 7 + internal/dependencies/tools/validator_test.go | 7 + internal/dependencies/tools/yq.go | 75 ++++++++++ internal/dependencies/tools/yq_test.go | 139 ++++++++++++++++++ internal/tool/bash/runner.go | 77 ++++++++++ internal/tool/bash/runner_test.go | 58 ++++++++ internal/tool/runner.go | 18 ++- internal/tool/yq/runner.go | 75 ++++++++++ internal/tool/yq/runner_test.go | 58 ++++++++ 18 files changed, 715 insertions(+), 19 deletions(-) create mode 100644 internal/dependencies/tools/bash.go create mode 100644 internal/dependencies/tools/bash_test.go create mode 100755 internal/dependencies/tools/test_data/yq/4.33.0/yq create mode 100755 internal/dependencies/tools/test_data/yq/4.34.1/yq create mode 100644 internal/dependencies/tools/yq.go create mode 100644 internal/dependencies/tools/yq_test.go create mode 100644 internal/tool/bash/runner.go create mode 100644 internal/tool/bash/runner_test.go create mode 100644 internal/tool/yq/runner.go create mode 100644 internal/tool/yq/runner_test.go diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index e58777730..318d3bdc7 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -103,6 +103,12 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error creating template config: %w", err) } + mCfg.Data["paths"] = map[any]any{ + "kubectl": d.OperationPhase.KubectlPath, + "kustomize": d.OperationPhase.KustomizePath, + "yq": d.OperationPhase.YqPath, + } + // Generate manifests. outYaml, err := yamlx.MarshalV2(mCfg) if err != nil { diff --git a/internal/cluster/phase.go b/internal/cluster/phase.go index ee1eeee71..5b50b4bf1 100644 --- a/internal/cluster/phase.go +++ b/internal/cluster/phase.go @@ -49,6 +49,7 @@ type OperationPhase struct { TerraformPath string KustomizePath string KubectlPath string + YqPath string PlanPath string LogsPath string OutputsPath string @@ -67,6 +68,7 @@ func NewOperationPhase(folder string, kfdTools config.KFDTools, binPath string) kustomizePath := path.Join(binPath, "kustomize", kfdTools.Common.Kustomize.Version, "kustomize") terraformPath := path.Join(binPath, "terraform", kfdTools.Common.Terraform.Version, "terraform") kubectlPath := path.Join(binPath, "kubectl", kfdTools.Common.Kubectl.Version, "kubectl") + yqPath := path.Join(binPath, "yq", kfdTools.Common.Yq.Version, "yq") planPath := path.Join(basePath, "terraform", "plan") logsPath := path.Join(basePath, "terraform", "logs") @@ -83,6 +85,7 @@ func NewOperationPhase(folder string, kfdTools config.KFDTools, binPath string) OutputsPath: outputsPath, SecretsPath: secretsPath, binPath: binPath, + YqPath: yqPath, }, nil } diff --git a/internal/dependencies/tools/bash.go b/internal/dependencies/tools/bash.go new file mode 100644 index 000000000..5a221bd2e --- /dev/null +++ b/internal/dependencies/tools/bash.go @@ -0,0 +1,59 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "fmt" + "regexp" + "runtime" + "strings" + + "github.com/sighupio/furyctl/internal/tool/bash" +) + +func NewBash(runner *bash.Runner, version string) *Bash { + return &Bash{ + arch: runtime.GOARCH, + os: runtime.GOOS, + version: version, + checker: &checker{ + regex: regexp.MustCompile(`version (\S*)\(`), + runner: runner, + splitFn: func(version string) []string { + return strings.Split(version, " ") + }, + trimFn: func(tokens []string) string { + return strings.TrimSuffix(tokens[len(tokens)-1], "(") + }, + }, + } +} + +type Bash struct { + arch string + checker *checker + os string + version string +} + +func (*Bash) SupportsDownload() bool { + return false +} + +func (*Bash) SrcPath() string { + return "" +} + +func (*Bash) Rename(_ string) error { + return nil +} + +func (a *Bash) CheckBinVersion() error { + if err := a.checker.version(a.version); err != nil { + return fmt.Errorf("bash: %w", err) + } + + return nil +} diff --git a/internal/dependencies/tools/bash_test.go b/internal/dependencies/tools/bash_test.go new file mode 100644 index 000000000..31ee92f52 --- /dev/null +++ b/internal/dependencies/tools/bash_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "strings" + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/bash" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Bash_SupportsDownload(t *testing.T) { + a := tools.NewBash(newBashRunner(), "3.2.57") + + if a.SupportsDownload() != false { + t.Errorf("bash download must not be supported") + } +} + +func Test_Bash_CheckBinVersion(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + wantVersion string + wantErr bool + wantErrMsg string + }{ + { + desc: "correct version installed", + wantVersion: "3.2.57", + }, + { + desc: "wrong version installed", + wantVersion: "1.2.57", + wantErr: true, + wantErrMsg: "installed = 3.2.57, expected = 1.2.57", + }, + } + for _, tC := range testCases { + tC := tC + + t.Run(tC.desc, func(t *testing.T) { + t.Parallel() + + fa := tools.NewBash(newBashRunner(), tC.wantVersion) + + err := fa.CheckBinVersion() + + if tC.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + + if !tC.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + + if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { + t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) + } + }) + } +} + +func newBashRunner() *bash.Runner { + return bash.NewRunner(execx.NewFakeExecutor(), bash.Paths{ + Bash: "bash", + }) +} diff --git a/internal/dependencies/tools/git.go b/internal/dependencies/tools/git.go index 390636f56..3ee73a4b9 100644 --- a/internal/dependencies/tools/git.go +++ b/internal/dependencies/tools/git.go @@ -16,7 +16,7 @@ import ( func NewGit(runner *git.Runner, version string) *Git { return &Git{ - arch: "amd64", + arch: runtime.GOARCH, os: runtime.GOOS, version: version, checker: &checker{ diff --git a/internal/dependencies/tools/test_data/yq/4.33.0/yq b/internal/dependencies/tools/test_data/yq/4.33.0/yq new file mode 100755 index 000000000..196f0dba0 --- /dev/null +++ b/internal/dependencies/tools/test_data/yq/4.33.0/yq @@ -0,0 +1,5 @@ +#!/bin/sh + +cat < Date: Tue, 20 Jun 2023 11:56:34 +0200 Subject: [PATCH 355/383] feat: rename bash to shell, run apply.sh script --- .../distribution/create/distribution.go | 52 +++---------- internal/dependencies/tools/bash.go | 59 --------------- internal/dependencies/tools/bash_test.go | 75 ------------------- internal/dependencies/tools/shell.go | 42 +++++++++++ internal/dependencies/tools/shell_test.go | 29 +++++++ internal/dependencies/tools/tool.go | 10 +-- internal/dependencies/tools/tool_test.go | 4 +- internal/dependencies/tools/validator.go | 6 +- internal/dependencies/tools/validator_test.go | 2 +- internal/tool/bash/runner_test.go | 58 -------------- internal/tool/runner.go | 10 +-- internal/tool/{bash => shell}/runner.go | 16 ++-- 12 files changed, 108 insertions(+), 255 deletions(-) delete mode 100644 internal/dependencies/tools/bash.go delete mode 100644 internal/dependencies/tools/bash_test.go create mode 100644 internal/dependencies/tools/shell.go create mode 100644 internal/dependencies/tools/shell_test.go delete mode 100644 internal/tool/bash/runner_test.go rename internal/tool/{bash => shell}/runner.go (79%) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 318d3bdc7..6ac741557 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -10,7 +10,6 @@ import ( "os" "path" "path/filepath" - "time" "github.com/sirupsen/logrus" @@ -21,6 +20,7 @@ import ( "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/shell" execx "github.com/sighupio/furyctl/internal/x/exec" yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) @@ -44,6 +44,7 @@ type Distribution struct { kzRunner *kustomize.Runner kubeRunner *kubectl.Runner dryRun bool + shellRunner *shell.Runner } func NewDistribution( @@ -82,6 +83,13 @@ func NewDistribution( true, false, ), + shellRunner: shell.NewRunner( + execx.NewStdExecutor(), + shell.Paths{ + Shell: "sh", + WorkDir: path.Join(phaseOp.Path, "manifests"), + }, + ), dryRun: dryRun, }, nil } @@ -216,11 +224,11 @@ func (d *Distribution) Exec() error { // Apply manifests. logrus.Info("Applying manifests...") - if err := d.delayedApplyRetries(manifestsOutPath, time.Minute, kubectlDelayMaxRetry); err != nil { - return err + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh")); err != nil { + return fmt.Errorf("error applying manifests: %w", err) } - return d.delayedApplyRetries(manifestsOutPath, 0, kubectlNoDelayMaxRetry) + return nil } func (*Distribution) Stop() error { @@ -264,39 +272,3 @@ func (d *Distribution) createFuryctlMerger() (*merge.Merger, error) { return reverseMerger, nil } - -func (d *Distribution) delayedApplyRetries(mPath string, delay time.Duration, maxRetries int) error { - var err error - - retries := 0 - - if maxRetries == 0 { - return nil - } - - err = d.kubeRunner.Apply(mPath) - if err == nil { - return nil - } - - retries++ - - for retries < maxRetries { - t := time.NewTimer(delay) - - if <-t.C; true { - logrus.Debug("applying manifests again... to ensure all resources are created.") - - err = d.kubeRunner.Apply(mPath) - if err == nil { - return nil - } - } - - retries++ - - t.Stop() - } - - return fmt.Errorf("error applying manifests: %w", err) -} diff --git a/internal/dependencies/tools/bash.go b/internal/dependencies/tools/bash.go deleted file mode 100644 index 5a221bd2e..000000000 --- a/internal/dependencies/tools/bash.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tools - -import ( - "fmt" - "regexp" - "runtime" - "strings" - - "github.com/sighupio/furyctl/internal/tool/bash" -) - -func NewBash(runner *bash.Runner, version string) *Bash { - return &Bash{ - arch: runtime.GOARCH, - os: runtime.GOOS, - version: version, - checker: &checker{ - regex: regexp.MustCompile(`version (\S*)\(`), - runner: runner, - splitFn: func(version string) []string { - return strings.Split(version, " ") - }, - trimFn: func(tokens []string) string { - return strings.TrimSuffix(tokens[len(tokens)-1], "(") - }, - }, - } -} - -type Bash struct { - arch string - checker *checker - os string - version string -} - -func (*Bash) SupportsDownload() bool { - return false -} - -func (*Bash) SrcPath() string { - return "" -} - -func (*Bash) Rename(_ string) error { - return nil -} - -func (a *Bash) CheckBinVersion() error { - if err := a.checker.version(a.version); err != nil { - return fmt.Errorf("bash: %w", err) - } - - return nil -} diff --git a/internal/dependencies/tools/bash_test.go b/internal/dependencies/tools/bash_test.go deleted file mode 100644 index 31ee92f52..000000000 --- a/internal/dependencies/tools/bash_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build unit - -package tools_test - -import ( - "strings" - "testing" - - "github.com/sighupio/furyctl/internal/dependencies/tools" - "github.com/sighupio/furyctl/internal/tool/bash" - execx "github.com/sighupio/furyctl/internal/x/exec" -) - -func Test_Bash_SupportsDownload(t *testing.T) { - a := tools.NewBash(newBashRunner(), "3.2.57") - - if a.SupportsDownload() != false { - t.Errorf("bash download must not be supported") - } -} - -func Test_Bash_CheckBinVersion(t *testing.T) { - t.Parallel() - - testCases := []struct { - desc string - wantVersion string - wantErr bool - wantErrMsg string - }{ - { - desc: "correct version installed", - wantVersion: "3.2.57", - }, - { - desc: "wrong version installed", - wantVersion: "1.2.57", - wantErr: true, - wantErrMsg: "installed = 3.2.57, expected = 1.2.57", - }, - } - for _, tC := range testCases { - tC := tC - - t.Run(tC.desc, func(t *testing.T) { - t.Parallel() - - fa := tools.NewBash(newBashRunner(), tC.wantVersion) - - err := fa.CheckBinVersion() - - if tC.wantErr && err == nil { - t.Errorf("expected error, got nil") - } - - if !tC.wantErr && err != nil { - t.Errorf("expected no error, got %v", err) - } - - if tC.wantErr && err != nil && !strings.Contains(err.Error(), tC.wantErrMsg) { - t.Errorf("expected error message '%s' to contain '%s'", err.Error(), tC.wantErrMsg) - } - }) - } -} - -func newBashRunner() *bash.Runner { - return bash.NewRunner(execx.NewFakeExecutor(), bash.Paths{ - Bash: "bash", - }) -} diff --git a/internal/dependencies/tools/shell.go b/internal/dependencies/tools/shell.go new file mode 100644 index 000000000..3c6fa6812 --- /dev/null +++ b/internal/dependencies/tools/shell.go @@ -0,0 +1,42 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tools + +import ( + "runtime" + + "github.com/sighupio/furyctl/internal/tool/shell" +) + +func NewShell(runner *shell.Runner, version string) *Shell { + return &Shell{ + arch: runtime.GOARCH, + os: runtime.GOOS, + version: version, + } +} + +type Shell struct { + arch string + checker *checker + os string + version string +} + +func (*Shell) SupportsDownload() bool { + return false +} + +func (*Shell) SrcPath() string { + return "" +} + +func (*Shell) Rename(_ string) error { + return nil +} + +func (a *Shell) CheckBinVersion() error { + return nil +} diff --git a/internal/dependencies/tools/shell_test.go b/internal/dependencies/tools/shell_test.go new file mode 100644 index 000000000..1064c20fb --- /dev/null +++ b/internal/dependencies/tools/shell_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package tools_test + +import ( + "testing" + + "github.com/sighupio/furyctl/internal/dependencies/tools" + "github.com/sighupio/furyctl/internal/tool/shell" + execx "github.com/sighupio/furyctl/internal/x/exec" +) + +func Test_Shell_SupportsDownload(t *testing.T) { + a := tools.NewShell(newShellRunner(), "3.2.57") + + if a.SupportsDownload() != false { + t.Errorf("shell download must not be supported") + } +} + +func newShellRunner() *shell.Runner { + return shell.NewRunner(execx.NewFakeExecutor(), shell.Paths{ + Shell: "sh", + }) +} diff --git a/internal/dependencies/tools/tool.go b/internal/dependencies/tools/tool.go index 8a1e067e8..f7de6d811 100644 --- a/internal/dependencies/tools/tool.go +++ b/internal/dependencies/tools/tool.go @@ -15,12 +15,12 @@ import ( "github.com/sighupio/furyctl/internal/tool" "github.com/sighupio/furyctl/internal/tool/ansible" "github.com/sighupio/furyctl/internal/tool/awscli" - "github.com/sighupio/furyctl/internal/tool/bash" "github.com/sighupio/furyctl/internal/tool/furyagent" "github.com/sighupio/furyctl/internal/tool/git" "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/openvpn" + "github.com/sighupio/furyctl/internal/tool/shell" "github.com/sighupio/furyctl/internal/tool/terraform" "github.com/sighupio/furyctl/internal/tool/yq" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -138,13 +138,13 @@ func (f *Factory) Create(name tool.Name, version string) Tool { return NewYq(yqr, version) - case tool.Bash: - bashr, ok := t.(*bash.Runner) + case tool.Shell: + shellr, ok := t.(*shell.Runner) if !ok { - panic(fmt.Sprintf("expected bash.Runner, got %T", t)) + panic(fmt.Sprintf("expected shell.Runner, got %T", t)) } - return NewBash(bashr, version) + return NewShell(shellr, version) default: return nil diff --git a/internal/dependencies/tools/tool_test.go b/internal/dependencies/tools/tool_test.go index a3f630648..2a88e7b96 100644 --- a/internal/dependencies/tools/tool_test.go +++ b/internal/dependencies/tools/tool_test.go @@ -46,7 +46,7 @@ func Test_Factory_Create(t *testing.T) { wantTool: true, }, { - desc: "bash", + desc: "shell", wantTool: true, }, { @@ -152,7 +152,7 @@ func TestHelperProcess(t *testing.T) { case "--version": fmt.Fprintf(os.Stdout, "yq (https://github.com/mikefarah/yq/) version v4.34.1") } - case "bash": + case "sh": switch subcmd { case "--version": fmt.Fprintf(os.Stdout, "GNU bash, version 3.2.57(1)-release (arm64-apple-darwin22)\nCopyright (C) 2007 Free Software Foundation, Inc.") diff --git a/internal/dependencies/tools/validator.go b/internal/dependencies/tools/validator.go index e95ae0b6c..06ae9d942 100644 --- a/internal/dependencies/tools/validator.go +++ b/internal/dependencies/tools/validator.go @@ -48,11 +48,11 @@ func (tv *Validator) ValidateBaseReqs() ([]string, []error) { oks = append(oks, "git") } - bash := tv.toolFactory.Create(itool.Bash, "*") - if err := bash.CheckBinVersion(); err != nil { + shell := tv.toolFactory.Create(itool.Shell, "*") + if err := shell.CheckBinVersion(); err != nil { errs = append(errs, err) } else { - oks = append(oks, "bash") + oks = append(oks, "shell") } return oks, errs diff --git a/internal/dependencies/tools/validator_test.go b/internal/dependencies/tools/validator_test.go index 206ef5ef7..8a87dad2c 100644 --- a/internal/dependencies/tools/validator_test.go +++ b/internal/dependencies/tools/validator_test.go @@ -167,7 +167,7 @@ func TestValidator_ValidateBaseReqs(t *testing.T) { desc: "all base requirements are met", wantOks: []string{ "git", - "bash", + "shell", }, }, } diff --git a/internal/tool/bash/runner_test.go b/internal/tool/bash/runner_test.go deleted file mode 100644 index b9b929d5d..000000000 --- a/internal/tool/bash/runner_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build unit - -package bash_test - -import ( - "fmt" - "os" - "testing" - - "github.com/sighupio/furyctl/internal/tool/bash" - execx "github.com/sighupio/furyctl/internal/x/exec" -) - -func Test_Runner_Version(t *testing.T) { - r := bash.NewRunner(execx.NewFakeExecutor(), bash.Paths{ - Bash: "bash", - WorkDir: os.TempDir(), - }) - - got, err := r.Version() - if err != nil { - t.Fatal(err) - } - - want := "GNU bash, version 3.2.57(1)-release (arm64-apple-darwin22)\nCopyright (C) 2007 Free Software Foundation, Inc." - - if got != want { - t.Errorf("expected version '%s', got '%s'", want, got) - } -} - -func TestHelperProcess(t *testing.T) { - args := os.Args - - if len(args) < 3 || args[1] != "-test.run=TestHelperProcess" { - return - } - - cmd, subcmd := args[3], args[4] - - switch cmd { - case "bash": - switch subcmd { - case "--version": - fmt.Fprintf(os.Stdout, "GNU bash, version 3.2.57(1)-release (arm64-apple-darwin22)\nCopyright (C) 2007 Free Software Foundation, Inc.") - default: - fmt.Fprintf(os.Stdout, "subcommand '%s' not found", subcmd) - } - default: - fmt.Fprintf(os.Stdout, "command '%s' not found", cmd) - } - - os.Exit(0) -} diff --git a/internal/tool/runner.go b/internal/tool/runner.go index 359397167..84d861bf4 100644 --- a/internal/tool/runner.go +++ b/internal/tool/runner.go @@ -9,12 +9,12 @@ import ( "github.com/sighupio/furyctl/internal/tool/ansible" "github.com/sighupio/furyctl/internal/tool/awscli" - "github.com/sighupio/furyctl/internal/tool/bash" "github.com/sighupio/furyctl/internal/tool/furyagent" "github.com/sighupio/furyctl/internal/tool/git" "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/openvpn" + "github.com/sighupio/furyctl/internal/tool/shell" "github.com/sighupio/furyctl/internal/tool/terraform" "github.com/sighupio/furyctl/internal/tool/yq" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -32,7 +32,7 @@ const ( Kustomize Name = "kustomize" Openvpn Name = "openvpn" Terraform Name = "terraform" - Bash Name = "bash" + Shell Name = "shell" ) type Runner interface { @@ -117,9 +117,9 @@ func (rf *RunnerFactory) Create(name Name, version, workDir string) Runner { WorkDir: workDir, }) - case Bash: - return bash.NewRunner(rf.executor, bash.Paths{ - Bash: string(name), + case Shell: + return shell.NewRunner(rf.executor, shell.Paths{ + Shell: "sh", WorkDir: workDir, }) diff --git a/internal/tool/bash/runner.go b/internal/tool/shell/runner.go similarity index 79% rename from internal/tool/bash/runner.go rename to internal/tool/shell/runner.go index 45850c3d2..f386b79cb 100644 --- a/internal/tool/bash/runner.go +++ b/internal/tool/shell/runner.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package bash +package shell import ( "fmt" @@ -13,7 +13,7 @@ import ( ) type Paths struct { - Bash string + Shell string WorkDir string } @@ -32,11 +32,11 @@ func NewRunner(executor execx.Executor, paths Paths) *Runner { } func (r *Runner) CmdPath() string { - return r.paths.Bash + return r.paths.Shell } func (r *Runner) newCmd(args []string) (*execx.Cmd, string) { - cmd := execx.NewCmd(r.paths.Bash, execx.CmdOptions{ + cmd := execx.NewCmd(r.paths.Shell, execx.CmdOptions{ Args: args, Executor: r.executor, WorkDir: r.paths.WorkDir, @@ -53,14 +53,16 @@ func (r *Runner) deleteCmd(id string) { } func (r *Runner) Version() (string, error) { - args := []string{"--version"} + return "", nil +} +func (r *Runner) Run(args ...string) (string, error) { cmd, id := r.newCmd(args) defer r.deleteCmd(id) out, err := execx.CombinedOutput(cmd) if err != nil { - return "", fmt.Errorf("error getting bash version: %w", err) + return "", fmt.Errorf("error while running shell: %w", err) } return out, nil @@ -69,7 +71,7 @@ func (r *Runner) Version() (string, error) { func (r *Runner) Stop() error { for _, cmd := range r.cmds { if err := cmd.Stop(); err != nil { - return fmt.Errorf("error stopping bash runner: %w", err) + return fmt.Errorf("error stopping shell runner: %w", err) } } From 667f46cb429e6964b94b668ac12c473799fe1f25 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:52:03 +0200 Subject: [PATCH 356/383] feat: added store of kfd.yaml secret (#394) * feat: added store of kfd.yaml secret * fix: key in secret --- .../apis/kfd/v1alpha2/distribution/creator.go | 50 +++++++++++++++--- internal/apis/kfd/v1alpha2/eks/creator.go | 52 ++++++++++++++++++- internal/x/kube/secret.go | 4 +- internal/x/kube/secret_test.go | 3 +- 4 files changed, 98 insertions(+), 11 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go index 5332168ec..2211126f9 100644 --- a/internal/apis/kfd/v1alpha2/distribution/creator.go +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -107,18 +107,20 @@ func (v *ClusterCreator) Create(_ string, _ int) error { return fmt.Errorf("error while installing Kubernetes Fury Distribution: %w", err) } - if !v.dryRun { - if err := v.storeClusterConfig(); err != nil { - return fmt.Errorf("error while storing cluster config: %w", err) - } - } - if v.dryRun { logrus.Info("Kubernetes Fury Distribution installed successfully (dry-run mode)") return nil } + if err := v.storeClusterConfig(); err != nil { + return fmt.Errorf("error while storing cluster config: %w", err) + } + + if err := v.storeDistributionConfig(); err != nil { + return fmt.Errorf("error while storing distribution config: %w", err) + } + logrus.Info("Kubernetes Fury Distribution installed successfully") return nil @@ -130,7 +132,7 @@ func (v *ClusterCreator) storeClusterConfig() error { return fmt.Errorf("error while reading config file: %w", err) } - secret, err := kubex.CreateSecret(x, "furyctl-config", "kube-system") + secret, err := kubex.CreateSecret(x, "furyctl-config", "config", "kube-system") if err != nil { return fmt.Errorf("error while creating secret: %w", err) } @@ -157,3 +159,37 @@ func (v *ClusterCreator) storeClusterConfig() error { return nil } + +func (v *ClusterCreator) storeDistributionConfig() error { + x, err := os.ReadFile(path.Join(v.paths.DistroPath, "kfd.yaml")) + if err != nil { + return fmt.Errorf("error while reading config file: %w", err) + } + + secret, err := kubex.CreateSecret(x, "furyctl-kfd", "kfd", "kube-system") + if err != nil { + return fmt.Errorf("error while creating secret: %w", err) + } + + secretPath := path.Join(v.paths.WorkDir, "secrets-kfd.yaml") + + if err := iox.WriteFile(secretPath, secret); err != nil { + return fmt.Errorf("error while writing secret: %w", err) + } + + defer os.Remove(secretPath) + + runner := kubectl.NewRunner(execx.NewStdExecutor(), kubectl.Paths{ + Kubectl: path.Join(v.paths.BinPath, "kubectl", v.kfdManifest.Tools.Common.Kubectl.Version, "kubectl"), + WorkDir: v.paths.WorkDir, + Kubeconfig: v.paths.Kubeconfig, + }, true, true, false) + + logrus.Info("Saving distribution configuration file in the cluster...") + + if err := runner.Apply(secretPath); err != nil { + return fmt.Errorf("error while saving distribution configuration file in the cluster: %w", err) + } + + return nil +} diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index abac8c5c5..7cf409f42 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -295,6 +295,10 @@ func (v *ClusterCreator) kubernetesPhase(kube *create.Kubernetes, vpnConnector * return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) } + if err := v.storeDistributionConfig(); err != nil { + return fmt.Errorf("error while creating secret with the distribution configuration: %w", err) + } + logrus.Info("Kubernetes cluster created successfully") if err := v.logKubeconfig(); err != nil { @@ -331,6 +335,10 @@ func (v *ClusterCreator) distributionPhase(distro *create.Distribution, vpnConne return fmt.Errorf("error while creating secret with the cluster configuration: %w", err) } + if err := v.storeDistributionConfig(); err != nil { + return fmt.Errorf("error while creating secret with the distribution configuration: %w", err) + } + logrus.Info("Kubernetes Fury Distribution installed successfully") if err := v.logVPNKill(vpnConnector); err != nil { @@ -384,6 +392,10 @@ func (v *ClusterCreator) allPhases( if err := v.storeClusterConfig(); err != nil { return fmt.Errorf("error while storing cluster config: %w", err) } + + if err := v.storeDistributionConfig(); err != nil { + return fmt.Errorf("error while creating secret with the distribution configuration: %w", err) + } } } @@ -396,6 +408,10 @@ func (v *ClusterCreator) allPhases( if err := v.storeClusterConfig(); err != nil { return fmt.Errorf("error while storing cluster config: %w", err) } + + if err := v.storeDistributionConfig(); err != nil { + return fmt.Errorf("error while creating secret with the distribution configuration: %w", err) + } } } @@ -483,7 +499,7 @@ func (v *ClusterCreator) storeClusterConfig() error { return fmt.Errorf("error while reading config file: %w", err) } - secret, err := kubex.CreateSecret(x, "furyctl-config", "kube-system") + secret, err := kubex.CreateSecret(x, "furyctl-config", "config", "kube-system") if err != nil { return fmt.Errorf("error while creating secret: %w", err) } @@ -510,3 +526,37 @@ func (v *ClusterCreator) storeClusterConfig() error { return nil } + +func (v *ClusterCreator) storeDistributionConfig() error { + x, err := os.ReadFile(path.Join(v.paths.DistroPath, "kfd.yaml")) + if err != nil { + return fmt.Errorf("error while reading config file: %w", err) + } + + secret, err := kubex.CreateSecret(x, "furyctl-kfd", "kfd", "kube-system") + if err != nil { + return fmt.Errorf("error while creating secret: %w", err) + } + + secretPath := path.Join(v.paths.WorkDir, "secrets-kfd.yaml") + + if err := iox.WriteFile(secretPath, secret); err != nil { + return fmt.Errorf("error while writing secret: %w", err) + } + + defer os.Remove(secretPath) + + runner := kubectl.NewRunner(execx.NewStdExecutor(), kubectl.Paths{ + Kubectl: path.Join(v.paths.BinPath, "kubectl", v.kfdManifest.Tools.Common.Kubectl.Version, "kubectl"), + WorkDir: v.paths.WorkDir, + Kubeconfig: v.paths.Kubeconfig, + }, true, true, false) + + logrus.Info("Saving distribution configuration file in the cluster...") + + if err := runner.Apply(secretPath); err != nil { + return fmt.Errorf("error while saving distribution configuration file in the cluster: %w", err) + } + + return nil +} diff --git a/internal/x/kube/secret.go b/internal/x/kube/secret.go index 1220970a2..4f7e10e70 100644 --- a/internal/x/kube/secret.go +++ b/internal/x/kube/secret.go @@ -11,7 +11,7 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -func CreateSecret(data []byte, name, namespace string) ([]byte, error) { +func CreateSecret(data []byte, name, key, namespace string) ([]byte, error) { secret := struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -27,7 +27,7 @@ func CreateSecret(data []byte, name, namespace string) ([]byte, error) { }, Type: "Opaque", Data: map[string]string{ - "config": base64.StdEncoding.EncodeToString(data), + key: base64.StdEncoding.EncodeToString(data), }, } diff --git a/internal/x/kube/secret_test.go b/internal/x/kube/secret_test.go index 10d8fc7d8..b892adc96 100644 --- a/internal/x/kube/secret_test.go +++ b/internal/x/kube/secret_test.go @@ -16,6 +16,7 @@ func TestCreateSecret(t *testing.T) { name := "test" namespace := "test" + key := "config" config := "dGVzdA==" secret := struct { @@ -42,7 +43,7 @@ func TestCreateSecret(t *testing.T) { t.Fatal(err) } - got, err := kubex.CreateSecret([]byte("test"), name, namespace) + got, err := kubex.CreateSecret([]byte("test"), name, key, namespace) if err != nil { t.Fatal(err) } From de007697b70c864028683c1c34bc014986f14f0b Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 20 Jun 2023 18:43:54 +0200 Subject: [PATCH 357/383] feat: use delete script --- .../distribution/create/distribution.go | 7 +- .../distribution/delete/distribution.go | 258 +++--------------- 2 files changed, 39 insertions(+), 226 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 6ac741557..d03eca571 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -186,12 +186,7 @@ func (d *Distribution) Exec() error { if _, err := d.kubeRunner.Version(); err != nil { logrus.Debugf("Got error while running cluster reachability check: %s", err) - if !d.dryRun { - return errClusterConnect - } - - logrus.Warnf("Cluster is unreachable, make sure it is reachable before " + - "running the command without --dry-run") + return errClusterConnect } logrus.Info("Checking if at least one storage class is available...") diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go index e774cd6ba..b5899eff3 100644 --- a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go @@ -10,14 +10,14 @@ import ( "os" "path" "path/filepath" - "time" "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/cluster" - "github.com/sighupio/furyctl/internal/kubernetes" + "github.com/sighupio/furyctl/internal/tool/kubectl" "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/shell" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" ) @@ -41,9 +41,10 @@ type Ingress struct { type Distribution struct { *cluster.OperationPhase - kzRunner *kustomize.Runner - kubeClient *kubernetes.Client - dryRun bool + kzRunner *kustomize.Runner + kubeRunner *kubectl.Runner + shellRunner *shell.Runner + dryRun bool } func NewDistribution( @@ -55,28 +56,37 @@ func NewDistribution( ) (*Distribution, error) { distroDir := path.Join(workDir, cluster.OperationPhaseDistribution) - phase, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, binPath) + phaseOp, err := cluster.NewOperationPhase(distroDir, kfdManifest.Tools, binPath) if err != nil { return nil, fmt.Errorf("error creating distribution phase: %w", err) } return &Distribution{ - OperationPhase: phase, + OperationPhase: phaseOp, kzRunner: kustomize.NewRunner( execx.NewStdExecutor(), kustomize.Paths{ - Kustomize: phase.KustomizePath, - WorkDir: path.Join(phase.Path, "manifests"), + Kustomize: phaseOp.KustomizePath, + WorkDir: path.Join(phaseOp.Path, "manifests"), }, ), - kubeClient: kubernetes.NewClient( - phase.KubectlPath, - path.Join(phase.Path, "manifests"), - kubeconfig, + kubeRunner: kubectl.NewRunner( + execx.NewStdExecutor(), + kubectl.Paths{ + Kubectl: phaseOp.KubectlPath, + WorkDir: path.Join(phaseOp.Path, "manifests"), + Kubeconfig: kubeconfig, + }, true, true, false, + ), + shellRunner: shell.NewRunner( execx.NewStdExecutor(), + shell.Paths{ + Shell: "sh", + WorkDir: path.Join(phaseOp.Path, "manifests"), + }, ), dryRun: dryRun, }, nil @@ -93,95 +103,17 @@ func (d *Distribution) Exec() error { return nil } - logrus.Info("Checking cluster connectivity...") - - if _, err := d.kubeClient.ToolVersion(); err != nil { - return errClusterConnect - } - - if d.dryRun { - manifestsOutPath, err := d.buildManifests() - if err != nil { - return err - } - - if _, err = d.kubeClient.DeleteFromPath(manifestsOutPath, "--dry-run=client"); err != nil { - logrus.Errorf("error while deleting resources: %v", err) - } - - logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") - - if _, err := d.kubeClient.ListNamespaceResources("ingress", "all"); err != nil { - logrus.Errorf("error while getting list of ingress resources: %v", err) - } - - if _, err := d.kubeClient.ListNamespaceResources("prometheus", "monitoring"); err != nil { - logrus.Errorf("error while getting list of prometheus resources: %v", err) - } - - if _, err := d.kubeClient.ListNamespaceResources("persistentvolumeclaim", "monitoring"); err != nil { - logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) - } - - if _, err := d.kubeClient.ListNamespaceResources("persistentvolumeclaim", "logging"); err != nil { - logrus.Errorf("error while getting list of persistentvolumeclaim resources: %v", err) - } - - if _, err := d.kubeClient.ListNamespaceResources("statefulset", "logging"); err != nil { - logrus.Errorf("error while getting list of statefulset resources: %v", err) - } - - if _, err := d.kubeClient.ListNamespaceResources("logging", "logging"); err != nil { - logrus.Errorf("error while getting list of logging resources: %v", err) - } - - if _, err := d.kubeClient.ListNamespaceResources("service", "ingress-nginx"); err != nil { - logrus.Errorf("error while getting list of service resources: %v", err) - } - - return nil - } - - logrus.Info("Deleting ingresses...") - - if err := d.deleteIngresses(); err != nil { - return err - } - - logrus.Warn("Deleting blocking resources, this operation will take a few minutes!") - - if err := d.deleteBlockingResources(); err != nil { - return err - } - + // Build manifests. logrus.Info("Building manifests...") - manifestsOutPath, err := d.buildManifests() - if err != nil { - return err - } - - logrus.Info("Deleting kubernetes resources...") - - _, err = d.kubeClient.DeleteFromPath(manifestsOutPath) - if err != nil { - logrus.Errorf("error while deleting resources: %v", err) - } - - logrus.Info("Checking pending resources...") - - return d.checkPendingResources() -} - -func (d *Distribution) buildManifests() (string, error) { kzOut, err := d.kzRunner.Build() if err != nil { - return "", fmt.Errorf("error building manifests: %w", err) + return fmt.Errorf("error building manifests: %w", err) } outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") if err != nil { - return "", fmt.Errorf("error creating temp dir: %w", err) + return fmt.Errorf("error creating temp dir: %w", err) } manifestsOutPath := filepath.Join(outDirPath, "out.yaml") @@ -189,143 +121,29 @@ func (d *Distribution) buildManifests() (string, error) { logrus.Debugf("built manifests = %s", manifestsOutPath) if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { - return "", fmt.Errorf("error writing built manifests: %w", err) - } - - return manifestsOutPath, nil -} - -func (d *Distribution) checkPendingResources() error { - var errSvc, errPv, errIgrs error - - var ingrs []kubernetes.Ingress - - var lbs, pvs []string - - dur := time.Second * checkPendingResourcesDelay - - maxRetries := checkPendingResourcesMaxRetries - - retries := 0 - - for retries < maxRetries { - p := time.NewTicker(dur) - - if <-p.C; true { - lbs, errSvc = d.kubeClient.GetLoadBalancers() - if errSvc == nil && len(lbs) > 0 { - errSvc = fmt.Errorf("%w: %v", errPendingResources, lbs) - } - - pvs, errPv = d.kubeClient.GetPersistentVolumes() - if errPv == nil && len(pvs) > 0 { - errPv = fmt.Errorf("%w: %v", errPendingResources, pvs) - } - - ingrs, errIgrs = d.kubeClient.GetIngresses() - if errIgrs == nil && len(ingrs) > 0 { - errIgrs = fmt.Errorf("%w: %v", errPendingResources, ingrs) - } - - if errSvc == nil && errPv == nil && errIgrs == nil { - return nil - } - } - - retries++ - - p.Stop() + return fmt.Errorf("error writing built manifests: %w", err) } - return fmt.Errorf("%w:\n%v\n%v\n%v", errCheckPendingResources, errSvc, errPv, errIgrs) -} + // Check cluster connection and requirements. + logrus.Info("Checking that the cluster is reachable...") -func (d *Distribution) deleteIngresses() error { - _, err := d.kubeClient.DeleteResourcesInAllNamespaces("ingress") - if err != nil { - return fmt.Errorf("error deleting ingresses: %w", err) - } - - return nil -} - -func (d *Distribution) deleteBlockingResources() error { - if err := d.deleteResource("deployment", "logging", "loki-distributed-distributor"); err != nil { - return err - } - - if err := d.deleteResource("deployment", "logging", "loki-distributed-compactor"); err != nil { - return err - } - - if err := d.deleteResources("prometheuses.monitoring.coreos.com", "monitoring"); err != nil { - return err - } - - if err := d.deleteResources("prometheusrules.monitoring.coreos.com", "monitoring"); err != nil { - return err - } - - if err := d.deleteResources("persistentvolumeclaims", "monitoring"); err != nil { - return err - } + if _, err := d.kubeRunner.Version(); err != nil { + logrus.Debugf("Got error while running cluster reachability check: %s", err) - if err := d.deleteResources("loggings.logging.banzaicloud.io", "logging"); err != nil { - return err - } - - if err := d.deleteResources("statefulsets.apps", "logging"); err != nil { - return err - } - - if err := d.deleteResources("persistentvolumeclaims", "logging"); err != nil { - return err - } - - if err := d.deleteResources("services", "ingress-nginx"); err != nil { - return err - } - - logrus.Debugf("waiting for resources to be deleted...") - - time.Sleep(time.Minute * ingressAfterDeleteDelay) - - return nil -} - -func (d *Distribution) deleteResource(typ, ns, name string) error { - logrus.Infof("Deleting %ss '%s' in namespace '%s'...\n", typ, name, ns) - - resExists, err := d.kubeClient.ResourceExists(name, typ, ns) - if err != nil { - return fmt.Errorf("error checking if %s '%s' exists in '%s' namespace: %w", typ, name, ns, err) + return errClusterConnect } - if resExists { - _, err = d.kubeClient.DeleteResource(name, typ, ns) - if err != nil { - return fmt.Errorf("error deleting %s '%s' in '%s' namespace: %w", typ, name, ns, err) + if d.dryRun { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "--dry-run=true"); err != nil { + return fmt.Errorf("error deleting resources: %w", err) } - } - return nil -} - -func (d *Distribution) deleteResources(typ, ns string) error { - logrus.Infof("Deleting %ss in namespace '%s'...\n", typ, ns) - - hasResTyp, err := d.kubeClient.HasResourceType(typ) - if err != nil { - return fmt.Errorf("error checking '%s' resources type: %w", typ, err) - } - - if !hasResTyp { return nil } - _, err = d.kubeClient.DeleteResources(typ, ns) - if err != nil { - return fmt.Errorf("error deleting '%s' in namespace '%s': %w", typ, ns, err) + // Delete manifests. + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh")); err != nil { + return fmt.Errorf("error deleting resources: %w", err) } return nil From 4561d1eb033b9c8f8ef5bf9edc3976f9a22a03f1 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 22 Jun 2023 16:35:55 +0200 Subject: [PATCH 358/383] feat: fix e2e tests and linting --- .../kfd/v1alpha2/distribution/create/distribution.go | 5 ----- .../kfd/v1alpha2/distribution/delete/distribution.go | 12 +----------- internal/dependencies/tools/shell.go | 5 ++--- internal/tool/shell/runner.go | 2 +- test/data/integration/v1.25.1/distro/kfd.yaml | 2 ++ 5 files changed, 6 insertions(+), 20 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index d03eca571..9b84b37aa 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -25,11 +25,6 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const ( - kubectlDelayMaxRetry = 3 - kubectlNoDelayMaxRetry = 7 -) - var ( errClusterConnect = errors.New("error connecting to cluster") errNoStorageClass = errors.New("at least one storage class is required") diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go index b5899eff3..0df25417e 100644 --- a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go @@ -22,17 +22,7 @@ import ( iox "github.com/sighupio/furyctl/internal/x/io" ) -const ( - ingressAfterDeleteDelay = 4 - checkPendingResourcesDelay = 20 - checkPendingResourcesMaxRetries = 5 -) - -var ( - errCheckPendingResources = errors.New("error while checking pending resources") - errPendingResources = errors.New("pending resources: ") - errClusterConnect = errors.New("error connecting to cluster") -) +var errClusterConnect = errors.New("error connecting to cluster") type Ingress struct { Name string diff --git a/internal/dependencies/tools/shell.go b/internal/dependencies/tools/shell.go index 3c6fa6812..1a4bed8d3 100644 --- a/internal/dependencies/tools/shell.go +++ b/internal/dependencies/tools/shell.go @@ -10,7 +10,7 @@ import ( "github.com/sighupio/furyctl/internal/tool/shell" ) -func NewShell(runner *shell.Runner, version string) *Shell { +func NewShell(_ *shell.Runner, version string) *Shell { return &Shell{ arch: runtime.GOARCH, os: runtime.GOOS, @@ -20,7 +20,6 @@ func NewShell(runner *shell.Runner, version string) *Shell { type Shell struct { arch string - checker *checker os string version string } @@ -37,6 +36,6 @@ func (*Shell) Rename(_ string) error { return nil } -func (a *Shell) CheckBinVersion() error { +func (*Shell) CheckBinVersion() error { return nil } diff --git a/internal/tool/shell/runner.go b/internal/tool/shell/runner.go index f386b79cb..c989788bd 100644 --- a/internal/tool/shell/runner.go +++ b/internal/tool/shell/runner.go @@ -52,7 +52,7 @@ func (r *Runner) deleteCmd(id string) { delete(r.cmds, id) } -func (r *Runner) Version() (string, error) { +func (*Runner) Version() (string, error) { return "", nil } diff --git a/test/data/integration/v1.25.1/distro/kfd.yaml b/test/data/integration/v1.25.1/distro/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/integration/v1.25.1/distro/kfd.yaml +++ b/test/data/integration/v1.25.1/distro/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" From 0423f3d108e392d7326f1b1f480f4e81c39ca598 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Fri, 23 Jun 2023 15:49:11 +0200 Subject: [PATCH 359/383] feat: add yq to e2e test data --- test/data/e2e/create/cluster/infrastructure/data/kfd.yaml | 2 ++ test/data/e2e/create/cluster/kubernetes/data/kfd.yaml | 2 ++ test/data/e2e/create/config/distro/kfd.yaml | 2 ++ test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml | 2 ++ test/data/e2e/dump/template/complex-dry-run/kfd.yaml | 2 ++ test/data/e2e/dump/template/complex/kfd.yaml | 2 ++ .../dump/template/distribution-yaml-no-data-property/kfd.yaml | 2 ++ test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml | 2 ++ test/data/e2e/dump/template/simple-dry-run/kfd.yaml | 2 ++ test/data/e2e/dump/template/simple/kfd.yaml | 2 ++ test/data/e2e/validate/config/correct/kfd.yaml | 2 ++ test/data/e2e/validate/config/wrong/kfd.yaml | 2 ++ test/data/e2e/validate/dependencies/correct/kfd.yaml | 2 ++ test/data/e2e/validate/dependencies/missing/kfd.yaml | 2 ++ test/data/e2e/validate/dependencies/wrong/kfd.yaml | 2 ++ test/data/expensive/common/data/kfd.yaml | 2 ++ 16 files changed, 32 insertions(+) diff --git a/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml index 1d36d9026..2eb5255d2 100644 --- a/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml +++ b/test/data/e2e/create/cluster/infrastructure/data/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.10.0 terraform: version: 1.4.6 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml b/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml index 1d36d9026..2eb5255d2 100644 --- a/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml +++ b/test/data/e2e/create/cluster/kubernetes/data/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.10.0 terraform: version: 1.4.6 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/create/config/distro/kfd.yaml b/test/data/e2e/create/config/distro/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/create/config/distro/kfd.yaml +++ b/test/data/e2e/create/config/distro/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml b/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml +++ b/test/data/e2e/download/dependencies/v1.25.1/distro/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/dump/template/complex-dry-run/kfd.yaml +++ b/test/data/e2e/dump/template/complex-dry-run/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/dump/template/complex/kfd.yaml b/test/data/e2e/dump/template/complex/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/dump/template/complex/kfd.yaml +++ b/test/data/e2e/dump/template/complex/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml +++ b/test/data/e2e/dump/template/distribution-yaml-no-data-property/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml +++ b/test/data/e2e/dump/template/no-furyctl-yaml/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/dump/template/simple-dry-run/kfd.yaml +++ b/test/data/e2e/dump/template/simple-dry-run/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/dump/template/simple/kfd.yaml b/test/data/e2e/dump/template/simple/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/dump/template/simple/kfd.yaml +++ b/test/data/e2e/dump/template/simple/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/validate/config/correct/kfd.yaml b/test/data/e2e/validate/config/correct/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/validate/config/correct/kfd.yaml +++ b/test/data/e2e/validate/config/correct/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/validate/config/wrong/kfd.yaml b/test/data/e2e/validate/config/wrong/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/validate/config/wrong/kfd.yaml +++ b/test/data/e2e/validate/config/wrong/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/validate/dependencies/correct/kfd.yaml b/test/data/e2e/validate/dependencies/correct/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/validate/dependencies/correct/kfd.yaml +++ b/test/data/e2e/validate/dependencies/correct/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/validate/dependencies/missing/kfd.yaml b/test/data/e2e/validate/dependencies/missing/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/validate/dependencies/missing/kfd.yaml +++ b/test/data/e2e/validate/dependencies/missing/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/e2e/validate/dependencies/wrong/kfd.yaml b/test/data/e2e/validate/dependencies/wrong/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/e2e/validate/dependencies/wrong/kfd.yaml +++ b/test/data/e2e/validate/dependencies/wrong/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" diff --git a/test/data/expensive/common/data/kfd.yaml b/test/data/expensive/common/data/kfd.yaml index e29673a88..7766c82a4 100644 --- a/test/data/expensive/common/data/kfd.yaml +++ b/test/data/expensive/common/data/kfd.yaml @@ -30,6 +30,8 @@ tools: version: 3.5.3 terraform: version: 0.15.4 + yq: + version: 4.34.1 eks: awscli: version: "*" From 6ed20cc7cb77d0d509c2b837e67919242ea6a01a Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Fri, 23 Jun 2023 16:04:12 +0200 Subject: [PATCH 360/383] tests: add yq fake binary --- test/data/e2e/validate/dependencies/correct/yq/4.34.1/yq | 5 +++++ test/data/e2e/validate/dependencies/wrong/yq/4.34.1/yq | 5 +++++ 2 files changed, 10 insertions(+) create mode 100755 test/data/e2e/validate/dependencies/correct/yq/4.34.1/yq create mode 100755 test/data/e2e/validate/dependencies/wrong/yq/4.34.1/yq diff --git a/test/data/e2e/validate/dependencies/correct/yq/4.34.1/yq b/test/data/e2e/validate/dependencies/correct/yq/4.34.1/yq new file mode 100755 index 000000000..b95158b0f --- /dev/null +++ b/test/data/e2e/validate/dependencies/correct/yq/4.34.1/yq @@ -0,0 +1,5 @@ +#!/bin/sh + +cat < Date: Tue, 27 Jun 2023 08:49:42 +0200 Subject: [PATCH 361/383] feat: remove kustomize build steps --- .../distribution/create/distribution.go | 30 ----------------- .../distribution/delete/distribution.go | 32 ------------------- 2 files changed, 62 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 9b84b37aa..3b36ba6bb 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -19,7 +19,6 @@ import ( "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/kubectl" - "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/shell" execx "github.com/sighupio/furyctl/internal/x/exec" yamlx "github.com/sighupio/furyctl/internal/x/yaml" @@ -36,7 +35,6 @@ type Distribution struct { furyctlConfPath string furyctlConf public.KfddistributionKfdV1Alpha2 distroPath string - kzRunner *kustomize.Runner kubeRunner *kubectl.Runner dryRun bool shellRunner *shell.Runner @@ -60,13 +58,6 @@ func NewDistribution( furyctlConf: furyctlConf, distroPath: paths.DistroPath, furyctlConfPath: paths.ConfigPath, - kzRunner: kustomize.NewRunner( - execx.NewStdExecutor(), - kustomize.Paths{ - Kustomize: phaseOp.KustomizePath, - WorkDir: path.Join(phaseOp.Path, "manifests"), - }, - ), kubeRunner: kubectl.NewRunner( execx.NewStdExecutor(), kubectl.Paths{ @@ -149,27 +140,6 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error generating from template files: %w", err) } - // Build manifests. - logrus.Info("Building manifests...") - - kzOut, err := d.kzRunner.Build() - if err != nil { - return fmt.Errorf("error building manifests: %w", err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") - if err != nil { - return fmt.Errorf("error creating temp dir: %w", err) - } - - manifestsOutPath := filepath.Join(outDirPath, "out.yaml") - - logrus.Debugf("built manifests = %s", manifestsOutPath) - - if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { - return fmt.Errorf("error writing built manifests: %w", err) - } - // Stop if dry run is enabled. if d.dryRun { return nil diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go index 0df25417e..db40fdeb4 100644 --- a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go @@ -7,16 +7,13 @@ package del import ( "errors" "fmt" - "os" "path" - "path/filepath" "github.com/sirupsen/logrus" "github.com/sighupio/fury-distribution/pkg/apis/config" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/tool/kubectl" - "github.com/sighupio/furyctl/internal/tool/kustomize" "github.com/sighupio/furyctl/internal/tool/shell" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" @@ -31,7 +28,6 @@ type Ingress struct { type Distribution struct { *cluster.OperationPhase - kzRunner *kustomize.Runner kubeRunner *kubectl.Runner shellRunner *shell.Runner dryRun bool @@ -53,13 +49,6 @@ func NewDistribution( return &Distribution{ OperationPhase: phaseOp, - kzRunner: kustomize.NewRunner( - execx.NewStdExecutor(), - kustomize.Paths{ - Kustomize: phaseOp.KustomizePath, - WorkDir: path.Join(phaseOp.Path, "manifests"), - }, - ), kubeRunner: kubectl.NewRunner( execx.NewStdExecutor(), kubectl.Paths{ @@ -93,27 +82,6 @@ func (d *Distribution) Exec() error { return nil } - // Build manifests. - logrus.Info("Building manifests...") - - kzOut, err := d.kzRunner.Build() - if err != nil { - return fmt.Errorf("error building manifests: %w", err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") - if err != nil { - return fmt.Errorf("error creating temp dir: %w", err) - } - - manifestsOutPath := filepath.Join(outDirPath, "out.yaml") - - logrus.Debugf("built manifests = %s", manifestsOutPath) - - if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { - return fmt.Errorf("error writing built manifests: %w", err) - } - // Check cluster connection and requirements. logrus.Info("Checking that the cluster is reachable...") From 140bbdff500102b5d0f4ca53e30fdcbab20e1e50 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 27 Jun 2023 09:03:47 +0200 Subject: [PATCH 362/383] feat: use apply and delete script on EKS --- .../distribution/create/distribution.go | 47 ++- .../distribution/delete/distribution.go | 2 + .../kfd/v1alpha2/eks/create/distribution.go | 114 ++---- .../kfd/v1alpha2/eks/delete/distribution.go | 339 +----------------- 4 files changed, 92 insertions(+), 410 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 3b36ba6bb..73db61594 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -10,6 +10,7 @@ import ( "os" "path" "path/filepath" + "sync" "github.com/sirupsen/logrus" @@ -142,6 +143,8 @@ func (d *Distribution) Exec() error { // Stop if dry run is enabled. if d.dryRun { + // TODO: build manifests without applying + return nil } @@ -191,7 +194,49 @@ func (d *Distribution) Exec() error { return nil } -func (*Distribution) Stop() error { +func (d *Distribution) Stop() error { + errCh := make(chan error) + doneCh := make(chan bool) + + var wg sync.WaitGroup + + //nolint:gomnd,revive // ignore magic number linters + wg.Add(2) + + go func() { + logrus.Debug("Stopping shell...") + + if err := d.shellRunner.Stop(); err != nil { + errCh <- fmt.Errorf("error stopping shell: %w", err) + } + + wg.Done() + }() + + go func() { + logrus.Debug("Stopping kubectl...") + + if err := d.kubeRunner.Stop(); err != nil { + errCh <- fmt.Errorf("error stopping kubectl: %w", err) + } + + wg.Done() + }() + + go func() { + wg.Wait() + close(doneCh) + }() + + select { + case <-doneCh: + + case err := <-errCh: + close(errCh) + + return err + } + return nil } diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go index db40fdeb4..14695220a 100644 --- a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go @@ -99,6 +99,8 @@ func (d *Distribution) Exec() error { return nil } + logrus.Info("Deleting kubernetes resources...") + // Delete manifests. if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh")); err != nil { return fmt.Errorf("error deleting resources: %w", err) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index dc5b45bf5..ce2b71835 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -22,7 +22,7 @@ import ( "github.com/sighupio/furyctl/internal/merge" "github.com/sighupio/furyctl/internal/template" "github.com/sighupio/furyctl/internal/tool/kubectl" - "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/shell" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" @@ -54,7 +54,7 @@ type Distribution struct { infraOutputsPath string distroPath string tfRunner *terraform.Runner - kzRunner *kustomize.Runner + shellRunner *shell.Runner kubeRunner *kubectl.Runner dryRun bool phase string @@ -96,11 +96,11 @@ func NewDistribution( Terraform: phaseOp.TerraformPath, }, ), - kzRunner: kustomize.NewRunner( + shellRunner: shell.NewRunner( execx.NewStdExecutor(), - kustomize.Paths{ - Kustomize: phaseOp.KustomizePath, - WorkDir: path.Join(phaseOp.Path, "manifests"), + shell.Paths{ + Shell: "sh", + WorkDir: path.Join(phaseOp.Path, "manifests"), }, ), kubeRunner: kubectl.NewRunner( @@ -176,15 +176,18 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error creating template config: %w", err) } - if err := d.copyFromTemplate(mCfg); err != nil { - return err + mCfg.Data["paths"] = map[any]any{ + "kubectl": d.OperationPhase.KubectlPath, + "kustomize": d.OperationPhase.KustomizePath, + "yq": d.OperationPhase.YqPath, } - _, err = d.buildManifests() - if err != nil { + if err := d.copyFromTemplate(mCfg); err != nil { return err } + // TODO: build manifests without applying + return nil } @@ -204,14 +207,13 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error creating template config: %w", err) } - if err := d.copyFromTemplate(mCfg); err != nil { - return err + mCfg.Data["paths"] = map[any]any{ + "kubectl": d.OperationPhase.KubectlPath, + "kustomize": d.OperationPhase.KustomizePath, + "yq": d.OperationPhase.YqPath, } - logrus.Info("Building manifests...") - - manifestsOutPath, err := d.buildManifests() - if err != nil { + if err := d.copyFromTemplate(mCfg); err != nil { return err } @@ -232,7 +234,7 @@ func (d *Distribution) Exec() error { logrus.Info("Applying manifests...") - return d.applyManifests(manifestsOutPath) + return d.applyManifests() } func (d *Distribution) Stop() error { @@ -255,10 +257,10 @@ func (d *Distribution) Stop() error { }() go func() { - logrus.Debug("Stopping kustomize...") + logrus.Debug("Stopping shell...") - if err := d.kzRunner.Stop(); err != nil { - errCh <- fmt.Errorf("error stopping kustomize: %w", err) + if err := d.shellRunner.Stop(); err != nil { + errCh <- fmt.Errorf("error stopping shell: %w", err) } wg.Done() @@ -604,74 +606,10 @@ func (d *Distribution) copyFromTemplate(cfg template.Config) error { return nil } -func (d *Distribution) buildManifests() (string, error) { - kzOut, err := d.kzRunner.Build() - if err != nil { - return "", fmt.Errorf("error building manifests: %w", err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") - if err != nil { - return "", fmt.Errorf("error creating temp dir: %w", err) - } - - manifestsOutPath := filepath.Join(outDirPath, "out.yaml") - - logrus.Debugf("built manifests = %s", manifestsOutPath) - - if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { - return "", fmt.Errorf("error writing built manifests: %w", err) - } - - return manifestsOutPath, nil -} - -func (d *Distribution) applyManifests(mPath string) error { - err := d.delayedApplyRetries(mPath, time.Minute, kubectlDelayMaxRetry) - if err == nil { - return nil - } - - err = d.delayedApplyRetries(mPath, 0, kubectlNoDelayMaxRetry) - if err == nil { - return nil - } - - return err -} - -func (d *Distribution) delayedApplyRetries(mPath string, delay time.Duration, maxRetries int) error { - var err error - - retries := 0 - - if maxRetries == 0 { - return nil +func (d *Distribution) applyManifests() error { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh")); err != nil { + return fmt.Errorf("error applying manifests: %w", err) } - err = d.kubeRunner.Apply(mPath) - if err == nil { - return nil - } - - retries++ - - for retries < maxRetries { - t := time.NewTimer(delay) - - if <-t.C; true { - logrus.Debug("applying manifests again... to ensure all resources are created.") - - err = d.kubeRunner.Apply(mPath) - if err == nil { - return nil - } - } - - retries++ - - t.Stop() - } - - return fmt.Errorf("error applying manifests: %w", err) + return nil } diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 2cf4ef87e..d8c0585b8 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -7,11 +7,8 @@ package del import ( "errors" "fmt" - "os" "path" - "path/filepath" "regexp" - "strings" "time" "github.com/sirupsen/logrus" @@ -20,11 +17,10 @@ import ( "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/kubernetes" "github.com/sighupio/furyctl/internal/tool/awscli" - "github.com/sighupio/furyctl/internal/tool/kustomize" + "github.com/sighupio/furyctl/internal/tool/shell" "github.com/sighupio/furyctl/internal/tool/terraform" execx "github.com/sighupio/furyctl/internal/x/exec" iox "github.com/sighupio/furyctl/internal/x/io" - "github.com/sighupio/furyctl/internal/x/slices" ) const ( @@ -48,11 +44,11 @@ type Ingress struct { type Distribution struct { *cluster.OperationPhase - tfRunner *terraform.Runner - kzRunner *kustomize.Runner - awsRunner *awscli.Runner - kubeClient *kubernetes.Client - dryRun bool + tfRunner *terraform.Runner + awsRunner *awscli.Runner + shellRunner *shell.Runner + kubeClient *kubernetes.Client + dryRun bool } func NewDistribution( @@ -81,13 +77,6 @@ func NewDistribution( Terraform: phase.TerraformPath, }, ), - kzRunner: kustomize.NewRunner( - execx.NewStdExecutor(), - kustomize.Paths{ - Kustomize: phase.KustomizePath, - WorkDir: path.Join(phase.Path, "manifests"), - }, - ), awsRunner: awscli.NewRunner( execx.NewStdExecutor(), awscli.Paths{ @@ -104,6 +93,13 @@ func NewDistribution( false, execx.NewStdExecutor(), ), + shellRunner: shell.NewRunner( + execx.NewStdExecutor(), + shell.Paths{ + Shell: "sh", + WorkDir: path.Join(phase.Path, "manifests"), + }, + ), dryRun: dryRun, }, nil } @@ -138,13 +134,8 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error running terraform plan: %w", err) } - manifestsOutPath, err := d.buildManifests() - if err != nil { - return err - } - - if _, err = d.kubeClient.DeleteFromPath(manifestsOutPath, "--dry-run=client"); err != nil { - logrus.Errorf("error while deleting resources: %v", err) + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "--dry-run=true"); err != nil { + return fmt.Errorf("error deleting resources: %w", err) } logrus.Info("The following resources, regardless of the built manifests, are going to be deleted:") @@ -180,311 +171,17 @@ func (d *Distribution) Exec() error { return nil } - ingressHosts, err := d.getIngressHosts() - if err != nil { - return err - } - - hostedZones, err := d.getHostedZones() - if err != nil { - return err - } - - logrus.Info("Deleting ingresses...") - - if err = d.deleteIngresses(); err != nil { - return err - } - - if len(ingressHosts) > 0 { - logrus.Info("Waiting for DNS records to be deleted...") - - if err = d.assertEmptyDNSRecords(ingressHosts, hostedZones); err != nil { - return err - } - } - - logrus.Warn("Deleting blocking resources, this operation will take a few minutes!") - - if err = d.deleteBlockingResources(); err != nil { - return err - } - - logrus.Info("Building manifests...") - - manifestsOutPath, err := d.buildManifests() - if err != nil { - return err - } - logrus.Info("Deleting kubernetes resources...") - _, err = d.kubeClient.DeleteFromPath(manifestsOutPath) - if err != nil { - logrus.Errorf("error while deleting resources: %v", err) - } - - logrus.Info("Checking pending resources...") - - if err = d.checkPendingResources(); err != nil { - return err + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh")); err != nil { + return fmt.Errorf("error deleting resources: %w", err) } logrus.Info("Deleting infra resources...") - if err = d.tfRunner.Destroy(); err != nil { + if err := d.tfRunner.Destroy(); err != nil { return fmt.Errorf("error while deleting infra resources: %w", err) } return nil } - -func (d *Distribution) buildManifests() (string, error) { - kzOut, err := d.kzRunner.Build() - if err != nil { - return "", fmt.Errorf("error building manifests: %w", err) - } - - outDirPath, err := os.MkdirTemp("", "furyctl-dist-manifests-") - if err != nil { - return "", fmt.Errorf("error creating temp dir: %w", err) - } - - manifestsOutPath := filepath.Join(outDirPath, "out.yaml") - - logrus.Debugf("built manifests = %s", manifestsOutPath) - - if err = os.WriteFile(manifestsOutPath, []byte(kzOut), os.ModePerm); err != nil { - return "", fmt.Errorf("error writing built manifests: %w", err) - } - - return manifestsOutPath, nil -} - -func (d *Distribution) checkPendingResources() error { - var errSvc, errPv, errIgrs error - - var ingrs []kubernetes.Ingress - - var lbs, pvs []string - - dur := time.Second * checkPendingResourcesDelay - - maxRetries := checkPendingResourcesMaxRetries - - retries := 0 - - for retries < maxRetries { - p := time.NewTicker(dur) - - if <-p.C; true { - lbs, errSvc = d.kubeClient.GetLoadBalancers() - if errSvc == nil && len(lbs) > 0 { - errSvc = fmt.Errorf("%w: %v", errPendingResources, lbs) - } - - pvs, errPv = d.kubeClient.GetPersistentVolumes() - if errPv == nil && len(pvs) > 0 { - errPv = fmt.Errorf("%w: %v", errPendingResources, pvs) - } - - ingrs, errIgrs = d.kubeClient.GetIngresses() - if errIgrs == nil && len(ingrs) > 0 { - errIgrs = fmt.Errorf("%w: %v", errPendingResources, ingrs) - } - - if errSvc == nil && errPv == nil && errIgrs == nil { - return nil - } - } - - retries++ - - p.Stop() - } - - return fmt.Errorf("%w:\n%v\n%v\n%v", errCheckPendingResources, errSvc, errPv, errIgrs) -} - -func (d *Distribution) deleteIngresses() error { - _, err := d.kubeClient.DeleteResourcesInAllNamespaces("ingress") - if err != nil { - return fmt.Errorf("error deleting ingresses: %w", err) - } - - return nil -} - -func (d *Distribution) deleteBlockingResources() error { - if err := d.deleteResource("deployment", "logging", "loki-distributed-distributor"); err != nil { - return err - } - - if err := d.deleteResource("deployment", "logging", "loki-distributed-compactor"); err != nil { - return err - } - - if err := d.deleteResources("prometheuses.monitoring.coreos.com", "monitoring"); err != nil { - return err - } - - if err := d.deleteResources("prometheusrules.monitoring.coreos.com", "monitoring"); err != nil { - return err - } - - if err := d.deleteResources("persistentvolumeclaims", "monitoring"); err != nil { - return err - } - - if err := d.deleteResources("loggings.logging.banzaicloud.io", "logging"); err != nil { - return err - } - - if err := d.deleteResources("statefulsets.apps", "logging"); err != nil { - return err - } - - if err := d.deleteResources("persistentvolumeclaims", "logging"); err != nil { - return err - } - - if err := d.deleteResources("services", "ingress-nginx"); err != nil { - return err - } - - logrus.Debugf("waiting for resources to be deleted...") - - time.Sleep(time.Minute * ingressAfterDeleteDelay) - - return nil -} - -func (d *Distribution) deleteResource(typ, ns, name string) error { - logrus.Infof("Deleting %ss '%s' in namespace '%s'...\n", typ, name, ns) - - resExists, err := d.kubeClient.ResourceExists(name, typ, ns) - if err != nil { - return fmt.Errorf("error checking if %s '%s' exists in '%s' namespace: %w", typ, name, ns, err) - } - - if resExists { - _, err = d.kubeClient.DeleteResource(name, typ, ns) - if err != nil { - return fmt.Errorf("error deleting %s '%s' in '%s' namespace: %w", typ, name, ns, err) - } - } - - return nil -} - -func (d *Distribution) deleteResources(typ, ns string) error { - logrus.Infof("Deleting %ss in namespace '%s'...\n", typ, ns) - - hasResTyp, err := d.kubeClient.HasResourceType(typ) - if err != nil { - return fmt.Errorf("error checking '%s' resources type: %w", typ, err) - } - - if !hasResTyp { - return nil - } - - _, err = d.kubeClient.DeleteResources(typ, ns) - if err != nil { - return fmt.Errorf("error deleting '%s' in namespace '%s': %w", typ, ns, err) - } - - return nil -} - -func (d *Distribution) getIngressHosts() ([]string, error) { - ingrs, err := d.kubeClient.GetIngresses() - if err != nil { - return nil, fmt.Errorf("error getting ingresses: %w", err) - } - - var hosts []string - - for _, ingress := range ingrs { - hosts = append(hosts, ingress.Host...) - } - - hosts = slices.Uniq(hosts) - - return hosts, nil -} - -func (d *Distribution) getHostedZones() (map[string]string, error) { - zones := make(map[string]string) - - route53, err := d.awsRunner.Route53("list-hosted-zones", "--query", "HostedZones[*].[Id,Name]", - "--output", "text") - if err != nil { - return zones, fmt.Errorf("error getting hosted zones: %w", err) - } - - matches := hostedZoneRegex.FindAllStringSubmatch(route53, -1) - - for _, match := range matches { - zones[match[1]] = match[2] - } - - return zones, nil -} - -func (d *Distribution) assertEmptyDNSRecords(hosts []string, hostedZones map[string]string) error { - if len(hosts) == 0 { - return nil - } - - queue := make([]string, 0, len(hostedZones)) - - for zone := range hostedZones { - queue = append(queue, zone) - } - - trimSuffix := func(a string) string { - return strings.TrimSuffix(a, ".") - } - - dur := time.Second * checkPendingResourcesDelay - - maxRetries := checkPendingResourcesMaxRetries * len(hosts) - - retries := 0 - - for retries < maxRetries { - p := time.NewTicker(dur) - - if <-p.C; true { - for _, zone := range queue { - domains, err := d.awsRunner.Route53("list-resource-record-sets", "--hosted-zone-id", zone, - "--query", "ResourceRecordSets[*].Name", "--output", "text") - if err != nil { - return fmt.Errorf("error getting hosted zone records: %w", err) - } - - matches := recordSetsRegex.FindAllString(domains, -1) - - if slices.DisjointTransform( - hosts, - matches, - nil, - trimSuffix, - ) { - queue = queue[1:] - } - } - - if len(queue) == 0 { - return nil - } - } - - retries++ - - p.Stop() - } - - return fmt.Errorf("%w: hostedzones %v", errCheckPendingResources, queue) -} From 801b3c9e54b10f8b03e8d08596fc6d755bba0a2c Mon Sep 17 00:00:00 2001 From: Alessio Pragliola Date: Tue, 27 Jun 2023 11:06:11 +0200 Subject: [PATCH 363/383] fix: create on EKSCluster kind --- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index ce2b71835..0c3b3cdb2 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -140,7 +140,7 @@ func (d *Distribution) Exec() error { return err } - tfCfg, err := template.NewConfig(furyctlMerger, preTfMerger, []string{"manifests", ".gitignore"}) + tfCfg, err := template.NewConfig(furyctlMerger, preTfMerger, []string{"manifests", "scripts", ".gitignore"}) if err != nil { return fmt.Errorf("error creating template config: %w", err) } @@ -197,6 +197,10 @@ func (d *Distribution) Exec() error { return fmt.Errorf("cannot create cloud resources: %w", err) } + if _, err := d.tfRunner.Output(); err != nil { + return fmt.Errorf("error running terraform output: %w", err) + } + postTfMerger, err := d.injectDataPostTf(preTfMerger) if err != nil { return err From cfc98546fb00330a027088ce5999d1463423d7a4 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 27 Jun 2023 11:13:48 +0200 Subject: [PATCH 364/383] feat: support dry-run on create --- .../distribution/create/distribution.go | 4 +++- .../kfd/v1alpha2/eks/create/distribution.go | 18 ++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 73db61594..27df02391 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -143,7 +143,9 @@ func (d *Distribution) Exec() error { // Stop if dry run is enabled. if d.dryRun { - // TODO: build manifests without applying + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "--dry-run=true"); err != nil { + return fmt.Errorf("error applying resources: %w", err) + } return nil } diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 0c3b3cdb2..30ba63e9b 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -186,7 +186,9 @@ func (d *Distribution) Exec() error { return err } - // TODO: build manifests without applying + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "--dry-run=true"); err != nil { + return fmt.Errorf("error applying manifests: %w", err) + } return nil } @@ -238,7 +240,11 @@ func (d *Distribution) Exec() error { logrus.Info("Applying manifests...") - return d.applyManifests() + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh")); err != nil { + return fmt.Errorf("error applying manifests: %w", err) + } + + return nil } func (d *Distribution) Stop() error { @@ -609,11 +615,3 @@ func (d *Distribution) copyFromTemplate(cfg template.Config) error { return nil } - -func (d *Distribution) applyManifests() error { - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh")); err != nil { - return fmt.Errorf("error applying manifests: %w", err) - } - - return nil -} From 7de7df1f1228bc5e63537c79ccaaeb9b3f1bbba0 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 27 Jun 2023 11:28:07 +0200 Subject: [PATCH 365/383] feat: pass kubeconfig to scripts --- .../kfd/v1alpha2/distribution/create/distribution.go | 9 ++++++--- internal/apis/kfd/v1alpha2/distribution/creator.go | 1 + .../kfd/v1alpha2/distribution/delete/distribution.go | 8 +++++--- internal/apis/kfd/v1alpha2/eks/create/distribution.go | 11 +++++++---- internal/apis/kfd/v1alpha2/eks/creator.go | 1 + internal/apis/kfd/v1alpha2/eks/delete/distribution.go | 8 +++++--- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 27df02391..564943321 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -39,6 +39,7 @@ type Distribution struct { kubeRunner *kubectl.Runner dryRun bool shellRunner *shell.Runner + kubeconfig string } func NewDistribution( @@ -46,6 +47,7 @@ func NewDistribution( furyctlConf public.KfddistributionKfdV1Alpha2, kfdManifest config.KFD, dryRun bool, + kubeconfig string, ) (*Distribution, error) { distroDir := path.Join(paths.WorkDir, cluster.OperationPhaseDistribution) @@ -77,7 +79,8 @@ func NewDistribution( WorkDir: path.Join(phaseOp.Path, "manifests"), }, ), - dryRun: dryRun, + dryRun: dryRun, + kubeconfig: kubeconfig, }, nil } @@ -143,7 +146,7 @@ func (d *Distribution) Exec() error { // Stop if dry run is enabled. if d.dryRun { - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "--dry-run=true"); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "true", d.kubeconfig); err != nil { return fmt.Errorf("error applying resources: %w", err) } @@ -189,7 +192,7 @@ func (d *Distribution) Exec() error { // Apply manifests. logrus.Info("Applying manifests...") - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh")); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "false", d.kubeconfig); err != nil { return fmt.Errorf("error applying manifests: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/distribution/creator.go b/internal/apis/kfd/v1alpha2/distribution/creator.go index 2211126f9..88abaf5ac 100644 --- a/internal/apis/kfd/v1alpha2/distribution/creator.go +++ b/internal/apis/kfd/v1alpha2/distribution/creator.go @@ -98,6 +98,7 @@ func (v *ClusterCreator) Create(_ string, _ int) error { v.furyctlConf, v.kfdManifest, v.dryRun, + v.paths.Kubeconfig, ) if err != nil { return fmt.Errorf("error while initiating distribution phase: %w", err) diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go index 14695220a..65ae71d0d 100644 --- a/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/delete/distribution.go @@ -31,6 +31,7 @@ type Distribution struct { kubeRunner *kubectl.Runner shellRunner *shell.Runner dryRun bool + kubeconfig string } func NewDistribution( @@ -67,7 +68,8 @@ func NewDistribution( WorkDir: path.Join(phaseOp.Path, "manifests"), }, ), - dryRun: dryRun, + dryRun: dryRun, + kubeconfig: kubeconfig, }, nil } @@ -92,7 +94,7 @@ func (d *Distribution) Exec() error { } if d.dryRun { - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "--dry-run=true"); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "true", d.kubeconfig); err != nil { return fmt.Errorf("error deleting resources: %w", err) } @@ -102,7 +104,7 @@ func (d *Distribution) Exec() error { logrus.Info("Deleting kubernetes resources...") // Delete manifests. - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh")); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "false", d.kubeconfig); err != nil { return fmt.Errorf("error deleting resources: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 30ba63e9b..34598d6a9 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -58,6 +58,7 @@ type Distribution struct { kubeRunner *kubectl.Runner dryRun bool phase string + kubeconfig string } type injectType struct { @@ -71,6 +72,7 @@ func NewDistribution( infraOutputsPath string, dryRun bool, phase string, + kubeconfig string, ) (*Distribution, error) { distroDir := path.Join(paths.WorkDir, cluster.OperationPhaseDistribution) @@ -114,8 +116,9 @@ func NewDistribution( true, false, ), - dryRun: dryRun, - phase: phase, + dryRun: dryRun, + phase: phase, + kubeconfig: kubeconfig, }, nil } @@ -186,7 +189,7 @@ func (d *Distribution) Exec() error { return err } - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "--dry-run=true"); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "true", d.kubeconfig); err != nil { return fmt.Errorf("error applying manifests: %w", err) } @@ -240,7 +243,7 @@ func (d *Distribution) Exec() error { logrus.Info("Applying manifests...") - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh")); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "apply.sh"), "false", d.kubeconfig); err != nil { return fmt.Errorf("error applying manifests: %w", err) } diff --git a/internal/apis/kfd/v1alpha2/eks/creator.go b/internal/apis/kfd/v1alpha2/eks/creator.go index 7cf409f42..07fa03b91 100644 --- a/internal/apis/kfd/v1alpha2/eks/creator.go +++ b/internal/apis/kfd/v1alpha2/eks/creator.go @@ -458,6 +458,7 @@ func (v *ClusterCreator) setupPhases() (*create.Infrastructure, *create.Kubernet infra.OutputsPath, v.dryRun, v.phase, + v.paths.Kubeconfig, ) if err != nil { return nil, nil, nil, fmt.Errorf("error while initiating distribution phase: %w", err) diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index d8c0585b8..5131cf3f6 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -49,6 +49,7 @@ type Distribution struct { shellRunner *shell.Runner kubeClient *kubernetes.Client dryRun bool + kubeconfig string } func NewDistribution( @@ -100,7 +101,8 @@ func NewDistribution( WorkDir: path.Join(phase.Path, "manifests"), }, ), - dryRun: dryRun, + kubeconfig: kubeconfig, + dryRun: dryRun, }, nil } @@ -134,7 +136,7 @@ func (d *Distribution) Exec() error { return fmt.Errorf("error running terraform plan: %w", err) } - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "--dry-run=true"); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "true", d.kubeconfig); err != nil { return fmt.Errorf("error deleting resources: %w", err) } @@ -173,7 +175,7 @@ func (d *Distribution) Exec() error { logrus.Info("Deleting kubernetes resources...") - if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh")); err != nil { + if _, err := d.shellRunner.Run(path.Join(d.Path, "scripts", "delete.sh"), "false", d.kubeconfig); err != nil { return fmt.Errorf("error deleting resources: %w", err) } From 80e97ac1a10441df44b8dad25ad37612f05a9508 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 27 Jun 2023 11:48:44 +0200 Subject: [PATCH 366/383] lint: fix linting --- .../v1alpha2/distribution/create/distribution.go | 2 +- .../apis/kfd/v1alpha2/eks/create/distribution.go | 5 ----- .../apis/kfd/v1alpha2/eks/delete/distribution.go | 15 +-------------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 564943321..d736e4d0b 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -205,7 +205,7 @@ func (d *Distribution) Stop() error { var wg sync.WaitGroup - //nolint:gomnd,revive // ignore magic number linters + //nolint:gomnd // ignore magic number linters wg.Add(2) go func() { diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 34598d6a9..027213ba3 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -29,11 +29,6 @@ import ( yamlx "github.com/sighupio/furyctl/internal/x/yaml" ) -const ( - kubectlDelayMaxRetry = 3 - kubectlNoDelayMaxRetry = 7 -) - var ( errCastingVpcIDToStr = errors.New("error casting vpc_id output to string") errCastingEbsIamToStr = errors.New("error casting ebs_csi_driver_iam_role_arn output to string") diff --git a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go index 5131cf3f6..15816668e 100644 --- a/internal/apis/kfd/v1alpha2/eks/delete/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/delete/distribution.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "path" - "regexp" "time" "github.com/sirupsen/logrus" @@ -23,19 +22,7 @@ import ( iox "github.com/sighupio/furyctl/internal/x/io" ) -const ( - ingressAfterDeleteDelay = 4 - checkPendingResourcesDelay = 20 - checkPendingResourcesMaxRetries = 5 -) - -var ( - errCheckPendingResources = errors.New("error while checking pending resources") - errPendingResources = errors.New("pending resources: ") - errClusterConnect = errors.New("error connecting to cluster") - hostedZoneRegex = regexp.MustCompile(`/hostedzone/(\S+)\t(\S+)\.`) - recordSetsRegex = regexp.MustCompile(`(\S+)\.`) -) +var errClusterConnect = errors.New("error connecting to cluster") type Ingress struct { Name string From 5eb9965208ed4925ac45e8662e669a59548ce35a Mon Sep 17 00:00:00 2001 From: Samuele Chiocca Date: Tue, 27 Jun 2023 16:01:17 +0200 Subject: [PATCH 367/383] feat(distro-provider): remove node ready check if spec.distribution.modules.networking.type is not none --- .../distribution/create/distribution.go | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index d736e4d0b..e6293d0c1 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -173,22 +173,27 @@ func (d *Distribution) Exec() error { return errNoStorageClass } - logrus.Info("Checking if all nodes are ready...") + if d.furyctlConf.Spec.Distribution.Modules.Networking.Type == "none" { + + logrus.Info("Checking if all nodes are ready...") + + getNotReadyNodesOutput, err := d.kubeRunner.Get( + "", + "nodes", + "--output", + "jsonpath=\"{range .items[*]}{.spec.taints[?(@.key==\"node.kubernetes.io/not-ready\")]}{end}\"", + ) + if err != nil { + return fmt.Errorf("error while checking nodes: %w", err) + } - getNotReadyNodesOutput, err := d.kubeRunner.Get( - "", - "nodes", - "--output", - "jsonpath=\"{range .items[*]}{.spec.taints[?(@.key==\"node.kubernetes.io/not-ready\")]}{end}\"", - ) - if err != nil { - return fmt.Errorf("error while checking nodes: %w", err) - } + if getNotReadyNodesOutput != "\"\"" { + return errNodesNotReady + } - if getNotReadyNodesOutput != "\"\"" { - return errNodesNotReady } + // Apply manifests. logrus.Info("Applying manifests...") From 350925757192bf665d219948696cd16a6e4006c2 Mon Sep 17 00:00:00 2001 From: Samuele Chiocca Date: Tue, 27 Jun 2023 16:57:44 +0200 Subject: [PATCH 368/383] feat: make velero_iam_role_arn optional if dr type is none on EKS provider --- .../kfd/v1alpha2/eks/create/distribution.go | 89 +++++++++++++------ 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 027213ba3..090edeb61 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -421,44 +421,81 @@ func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, e return nil, err } - injectData := injectType{ - Data: private.SpecDistribution{ - Modules: private.SpecDistributionModules{ - Aws: &private.SpecDistributionModulesAws{ - EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ - IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), - }, - LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ - IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), - }, - ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ - IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), + var injectData injectType + + // TODO make me beautiful + if d.furyctlConf.Spec.Distribution.Modules.Dr.Type == "eks" { + injectData = injectType{ + Data: private.SpecDistribution{ + Modules: private.SpecDistributionModules{ + Aws: &private.SpecDistributionModulesAws{ + EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ + IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), + }, + LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ + IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), + }, + ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ + IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), + }, }, - }, - Ingress: private.SpecDistributionModulesIngress{ - ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ - PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), - PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), + Ingress: private.SpecDistributionModulesIngress{ + ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ + PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), + PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), + }, + CertManager: private.SpecDistributionModulesIngressCertManager{ + ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ + Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ + IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), + }, + }, + }, }, - CertManager: private.SpecDistributionModulesIngressCertManager{ - ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ - Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ - IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), + Dr: private.SpecDistributionModulesDr{ + Velero: private.SpecDistributionModulesDrVelero{ + Eks: private.SpecDistributionModulesDrVeleroEks{ + IamRoleArn: private.TypesAwsArn(arns["velero_iam_role_arn"]), }, }, }, }, - Dr: private.SpecDistributionModulesDr{ - Velero: private.SpecDistributionModulesDrVelero{ - Eks: private.SpecDistributionModulesDrVeleroEks{ - IamRoleArn: private.TypesAwsArn(arns["velero_iam_role_arn"]), + }, + } + }else{ + injectData = injectType{ + Data: private.SpecDistribution{ + Modules: private.SpecDistributionModules{ + Aws: &private.SpecDistributionModulesAws{ + EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ + IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), + }, + LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ + IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), + }, + ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ + IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), + }, + }, + Ingress: private.SpecDistributionModulesIngress{ + ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ + PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), + PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), + }, + CertManager: private.SpecDistributionModulesIngressCertManager{ + ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ + Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ + IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), + }, + }, }, }, }, }, - }, + } } + injectDataModel := merge.NewDefaultModelFromStruct(injectData, ".data", true) merger := merge.NewMerger( From 4a909a4df4159d33df0045baac8197d7aba21ebf Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Tue, 27 Jun 2023 17:43:10 +0200 Subject: [PATCH 369/383] lint: format files --- internal/apis/kfd/v1alpha2/distribution/create/distribution.go | 1 - internal/apis/kfd/v1alpha2/eks/create/distribution.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index e6293d0c1..4fec11516 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -193,7 +193,6 @@ func (d *Distribution) Exec() error { } - // Apply manifests. logrus.Info("Applying manifests...") diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index 090edeb61..bce2d94ae 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -462,7 +462,7 @@ func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, e }, }, } - }else{ + } else { injectData = injectType{ Data: private.SpecDistribution{ Modules: private.SpecDistributionModules{ @@ -495,7 +495,6 @@ func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, e } } - injectDataModel := merge.NewDefaultModelFromStruct(injectData, ".data", true) merger := merge.NewMerger( From 201e7f8e7559f34d0569d8aabfebb0c0b07a07e8 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 28 Jun 2023 15:30:38 +0200 Subject: [PATCH 370/383] lint: fix linting and improve code --- .../distribution/create/distribution.go | 2 - .../kfd/v1alpha2/eks/create/distribution.go | 89 ++++++------------- 2 files changed, 28 insertions(+), 63 deletions(-) diff --git a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go index 4fec11516..0877d6ae4 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/distribution.go @@ -174,7 +174,6 @@ func (d *Distribution) Exec() error { } if d.furyctlConf.Spec.Distribution.Modules.Networking.Type == "none" { - logrus.Info("Checking if all nodes are ready...") getNotReadyNodesOutput, err := d.kubeRunner.Get( @@ -190,7 +189,6 @@ func (d *Distribution) Exec() error { if getNotReadyNodesOutput != "\"\"" { return errNodesNotReady } - } // Apply manifests. diff --git a/internal/apis/kfd/v1alpha2/eks/create/distribution.go b/internal/apis/kfd/v1alpha2/eks/create/distribution.go index bce2d94ae..f36c2c4a5 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/distribution.go +++ b/internal/apis/kfd/v1alpha2/eks/create/distribution.go @@ -421,77 +421,44 @@ func (d *Distribution) injectDataPostTf(fMerger *merge.Merger) (*merge.Merger, e return nil, err } - var injectData injectType - - // TODO make me beautiful - if d.furyctlConf.Spec.Distribution.Modules.Dr.Type == "eks" { - injectData = injectType{ - Data: private.SpecDistribution{ - Modules: private.SpecDistributionModules{ - Aws: &private.SpecDistributionModulesAws{ - EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ - IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), - }, - LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ - IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), - }, - ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ - IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), - }, + injectData := injectType{ + Data: private.SpecDistribution{ + Modules: private.SpecDistributionModules{ + Aws: &private.SpecDistributionModulesAws{ + EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ + IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), }, - Ingress: private.SpecDistributionModulesIngress{ - ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ - PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), - PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), - }, - CertManager: private.SpecDistributionModulesIngressCertManager{ - ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ - Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ - IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), - }, - }, - }, + LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ + IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), }, - Dr: private.SpecDistributionModulesDr{ - Velero: private.SpecDistributionModulesDrVelero{ - Eks: private.SpecDistributionModulesDrVeleroEks{ - IamRoleArn: private.TypesAwsArn(arns["velero_iam_role_arn"]), - }, - }, + ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ + IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), }, }, - }, - } - } else { - injectData = injectType{ - Data: private.SpecDistribution{ - Modules: private.SpecDistributionModules{ - Aws: &private.SpecDistributionModulesAws{ - EbsCsiDriver: private.SpecDistributionModulesAwsEbsCsiDriver{ - IamRoleArn: private.TypesAwsArn(arns["ebs_csi_driver_iam_role_arn"]), - }, - LoadBalancerController: private.SpecDistributionModulesAwsLoadBalancerController{ - IamRoleArn: private.TypesAwsArn(arns["load_balancer_controller_iam_role_arn"]), - }, - ClusterAutoscaler: private.SpecDistributionModulesAwsClusterAutoscaler{ - IamRoleArn: private.TypesAwsArn(arns["cluster_autoscaler_iam_role_arn"]), - }, + Ingress: private.SpecDistributionModulesIngress{ + ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ + PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), + PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), }, - Ingress: private.SpecDistributionModulesIngress{ - ExternalDns: private.SpecDistributionModulesIngressExternalDNS{ - PrivateIamRoleArn: private.TypesAwsArn(arns["external_dns_private_iam_role_arn"]), - PublicIamRoleArn: private.TypesAwsArn(arns["external_dns_public_iam_role_arn"]), - }, - CertManager: private.SpecDistributionModulesIngressCertManager{ - ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ - Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ - IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), - }, + CertManager: private.SpecDistributionModulesIngressCertManager{ + ClusterIssuer: private.SpecDistributionModulesIngressCertManagerClusterIssuer{ + Route53: private.SpecDistributionModulesIngressClusterIssuerRoute53{ + IamRoleArn: private.TypesAwsArn(arns["cert_manager_iam_role_arn"]), }, }, }, }, }, + }, + } + + if d.furyctlConf.Spec.Distribution.Modules.Dr.Type == "eks" { + injectData.Data.Modules.Dr = private.SpecDistributionModulesDr{ + Velero: private.SpecDistributionModulesDrVelero{ + Eks: private.SpecDistributionModulesDrVeleroEks{ + IamRoleArn: private.TypesAwsArn(arns["velero_iam_role_arn"]), + }, + }, } } From 3b866151fdff789c0347498cbb03bd34baa43308 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Thu, 29 Jun 2023 12:20:27 +0200 Subject: [PATCH 371/383] test: fix tests --- go.mod | 2 +- go.sum | 6 ++++++ internal/dependencies/tools/shell_test.go | 2 +- internal/dependencies/tools/yq_test.go | 2 +- internal/tool/yq/runner_test.go | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index dd3067921..747bfef6a 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26 + github.com/sighupio/fury-distribution v1.25.3-0.20230630103158-e63ee68f74e3 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index b2bf31333..c2e7f2a1c 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -232,9 +233,11 @@ github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= @@ -287,6 +290,7 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY= github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -296,6 +300,8 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sighupio/fury-distribution v1.25.3-0.20230630103158-e63ee68f74e3 h1:ItZA3l2MSos2ZQLnduhhFrrDuFj2xlU1EItRrohpT40= +github.com/sighupio/fury-distribution v1.25.3-0.20230630103158-e63ee68f74e3/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26 h1:kGHZ0NCjijAlDVseu0XJ/WHut9lvcMB9ujLZhWy7fsE= github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= diff --git a/internal/dependencies/tools/shell_test.go b/internal/dependencies/tools/shell_test.go index 1064c20fb..9be468bed 100644 --- a/internal/dependencies/tools/shell_test.go +++ b/internal/dependencies/tools/shell_test.go @@ -23,7 +23,7 @@ func Test_Shell_SupportsDownload(t *testing.T) { } func newShellRunner() *shell.Runner { - return shell.NewRunner(execx.NewFakeExecutor(), shell.Paths{ + return shell.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), shell.Paths{ Shell: "sh", }) } diff --git a/internal/dependencies/tools/yq_test.go b/internal/dependencies/tools/yq_test.go index ab3fa9d97..4ceb87978 100644 --- a/internal/dependencies/tools/yq_test.go +++ b/internal/dependencies/tools/yq_test.go @@ -133,7 +133,7 @@ func Test_Yq_CheckBinVersion(t *testing.T) { } func newYqRunner() *yq.Runner { - return yq.NewRunner(execx.NewFakeExecutor(), yq.Paths{ + return yq.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), yq.Paths{ Yq: "yq", }) } diff --git a/internal/tool/yq/runner_test.go b/internal/tool/yq/runner_test.go index f567a07d9..a22ed5f66 100644 --- a/internal/tool/yq/runner_test.go +++ b/internal/tool/yq/runner_test.go @@ -16,7 +16,7 @@ import ( ) func Test_Runner_Version(t *testing.T) { - r := yq.NewRunner(execx.NewFakeExecutor(), yq.Paths{ + r := yq.NewRunner(execx.NewFakeExecutor("TestHelperProcess"), yq.Paths{ Yq: "yq", WorkDir: os.TempDir(), }) From 10f6460af23b7b72a4001550a790910d6d66a847 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Mon, 3 Jul 2023 10:42:02 +0200 Subject: [PATCH 372/383] fix: check tplSource nil --- go.mod | 2 +- go.sum | 2 ++ internal/template/config.go | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 747bfef6a..fe98c66ee 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.3-0.20230630103158-e63ee68f74e3 + github.com/sighupio/fury-distribution v1.25.4-0.20230703100332-a87399c2785b github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index c2e7f2a1c..af20489c6 100644 --- a/go.sum +++ b/go.sum @@ -304,6 +304,8 @@ github.com/sighupio/fury-distribution v1.25.3-0.20230630103158-e63ee68f74e3 h1:I github.com/sighupio/fury-distribution v1.25.3-0.20230630103158-e63ee68f74e3/go.mod h1:GP3AWJD0nw1WSdHDHZoXbKXlpUOTkLAr9zKHIxmsOWM= github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26 h1:kGHZ0NCjijAlDVseu0XJ/WHut9lvcMB9ujLZhWy7fsE= github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= +github.com/sighupio/fury-distribution v1.25.4-0.20230703100332-a87399c2785b h1:vH8RFn/gu5oNOdj1Rc9LnJDCmT+3aaveN4PyBbomTBE= +github.com/sighupio/fury-distribution v1.25.4-0.20230703100332-a87399c2785b/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= diff --git a/internal/template/config.go b/internal/template/config.go index 66dfea4f0..b67125901 100644 --- a/internal/template/config.go +++ b/internal/template/config.go @@ -36,7 +36,7 @@ type Config struct { func NewConfig(tplSource, data *merge.Merger, excluded []string) (Config, error) { var cfg Config - if *tplSource.GetCustom() == nil { + if tplSource == nil || *tplSource.GetCustom() == nil { return cfg, ErrTemplateSourceCustomIsNil } @@ -70,7 +70,7 @@ func NewConfig(tplSource, data *merge.Merger, excluded []string) (Config, error) func NewConfigWithoutData(tplSource *merge.Merger, excluded []string) (Config, error) { var cfg Config - if *tplSource.GetCustom() == nil { + if tplSource == nil || *tplSource.GetCustom() == nil { return cfg, ErrTemplateSourceCustomIsNil } From 3b0514fac688201a049831aaf81ae4a0b8986b53 Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:26:07 +0200 Subject: [PATCH 373/383] Feature/enrich mixpanel data (#405) * feat: enrich mixpanel data + fix missing fields * fix: linting --- .goreleaser.yml | 5 +++-- cmd/create/cluster.go | 14 +++++++------- cmd/create/config.go | 1 + cmd/delete/cluster.go | 7 +++++++ cmd/download/dependencies.go | 9 +++++---- cmd/dump/template.go | 6 ++++++ cmd/legacy/vendor.go | 9 +++++++++ cmd/validate/config.go | 1 + cmd/validate/dependencies.go | 1 + internal/analytics/event.go | 1 + internal/analytics/tracker.go | 23 +++++++++++++++++------ main.go | 6 +++++- 12 files changed, 63 insertions(+), 20 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index bbf55429c..a798d538e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -10,6 +10,7 @@ before: builds: - env: - CGO_ENABLED=0 + - MIX_PANEL_TOKEN={{ if index .Env "FURYCTL_MIXPANEL_TOKEN" }}{{ .Env.FURYCTL_MIXPANEL_TOKEN }}{{ else }}""{{ end }} goos: - linux - windows @@ -18,13 +19,13 @@ builds: - amd64 - arm64 ldflags: - - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} -X main.mixPanelToken={{.Env.FURYCTL_MIXPANEL_TOKEN}} + - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} -X main.mixPanelToken={{.Env.MIX_PANEL_TOKEN}} archives: - name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}" checksum: name_template: 'checksums.txt' snapshot: - name_template: "{{ incpatch .Version }}-ng" + name_template: "{{ incpatch .Version }}-develop" changelog: sort: asc filters: diff --git a/cmd/create/cluster.go b/cmd/create/cluster.go index 23e9616bb..2e3f1630e 100644 --- a/cmd/create/cluster.go +++ b/cmd/create/cluster.go @@ -141,13 +141,6 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { // Download the distribution. logrus.Info("Downloading distribution...") res, err := distrodl.Download(flags.DistroLocation, flags.FuryctlPath) - - cmdEvent.AddClusterDetails(analytics.ClusterDetails{ - Provider: res.DistroManifest.Kubernetes.Eks.Version, - KFDVersion: res.DistroManifest.Version, - Phase: flags.Phase, - }) - if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) @@ -155,6 +148,13 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("error while downloading distribution: %w", err) } + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: res.MinimalConf.Kind, + KFDVersion: res.DistroManifest.Version, + Phase: flags.Phase, + DryRun: flags.DryRun, + }) + basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) // Init second half of collaborators. diff --git a/cmd/create/config.go b/cmd/create/config.go index 7e926a15f..854496b06 100644 --- a/cmd/create/config.go +++ b/cmd/create/config.go @@ -141,6 +141,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { } cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: res.MinimalConf.Kind, KFDVersion: res.DistroManifest.Version, }) diff --git a/cmd/delete/cluster.go b/cmd/delete/cluster.go index 249999d24..8170ce8f9 100644 --- a/cmd/delete/cluster.go +++ b/cmd/delete/cluster.go @@ -130,6 +130,13 @@ func NewClusterCmd(tracker *analytics.Tracker) *cobra.Command { return err } + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: res.MinimalConf.Kind, + KFDVersion: res.DistroManifest.Version, + Phase: flags.Phase, + DryRun: flags.DryRun, + }) + basePath := filepath.Join(homeDir, ".furyctl", res.MinimalConf.Metadata.Name) // Validate the dependencies. diff --git a/cmd/download/dependencies.go b/cmd/download/dependencies.go index 15fa74a97..d35db7d9e 100644 --- a/cmd/download/dependencies.go +++ b/cmd/download/dependencies.go @@ -80,10 +80,6 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { } dres, err := distrodl.Download(distroLocation, furyctlPath) - cmdEvent.AddClusterDetails(analytics.ClusterDetails{ - KFDVersion: dres.DistroManifest.Version, - }) - if err != nil { cmdEvent.AddErrorMessage(err) tracker.Track(cmdEvent) @@ -91,6 +87,11 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { return fmt.Errorf("failed to download distribution: %w", err) } + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: dres.MinimalConf.Kind, + KFDVersion: dres.DistroManifest.Version, + }) + basePath := filepath.Join(homeDir, ".furyctl", dres.MinimalConf.Metadata.Name) depsdl := dependencies.NewDownloader(client, basePath, binPath, https) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 0e44e6eeb..86eb43e57 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -78,6 +78,12 @@ The generated folder will be created starting from a provided templates folder a return fmt.Errorf("error downloading distribution: %w", err) } + cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: res.MinimalConf.Kind, + KFDVersion: res.DistroManifest.Version, + DryRun: flags.DryRun, + }) + if !flags.SkipValidation { // Validate the furyctl.yaml file. logrus.Info("Validating configuration file...") diff --git a/cmd/legacy/vendor.go b/cmd/legacy/vendor.go index 9b2eb7468..59c3f7f01 100644 --- a/cmd/legacy/vendor.go +++ b/cmd/legacy/vendor.go @@ -49,11 +49,17 @@ func NewVendorCmd(tracker *analytics.Tracker) *cobra.Command { ff, err := legacy.NewFuryFile(flags.FuryFilePath) if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return fmt.Errorf("%w: %v", ErrParsingFuryFile, err) } ps, err := ff.BuildPackages(flags.Prefix) if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return fmt.Errorf("%w: %v", ErrParsingPackages, err) } @@ -77,6 +83,9 @@ func NewVendorCmd(tracker *analytics.Tracker) *cobra.Command { err = downloader.Download(ps) if err != nil { + cmdEvent.AddErrorMessage(err) + tracker.Track(cmdEvent) + return fmt.Errorf("%w: %v", ErrDownloading, err) } diff --git a/cmd/validate/config.go b/cmd/validate/config.go index 8de20e0c4..cfde702a7 100644 --- a/cmd/validate/config.go +++ b/cmd/validate/config.go @@ -74,6 +74,7 @@ func NewConfigCmd(tracker *analytics.Tracker) *cobra.Command { } cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: res.MinimalConf.Kind, KFDVersion: res.DistroManifest.Version, }) diff --git a/cmd/validate/dependencies.go b/cmd/validate/dependencies.go index e0fd21f81..b50fcc215 100644 --- a/cmd/validate/dependencies.go +++ b/cmd/validate/dependencies.go @@ -83,6 +83,7 @@ func NewDependenciesCmd(tracker *analytics.Tracker) *cobra.Command { } cmdEvent.AddClusterDetails(analytics.ClusterDetails{ + Provider: dres.MinimalConf.Kind, KFDVersion: dres.DistroManifest.Version, }) diff --git a/internal/analytics/event.go b/internal/analytics/event.go index bca1ab732..1942e91d4 100644 --- a/internal/analytics/event.go +++ b/internal/analytics/event.go @@ -95,4 +95,5 @@ type ClusterDetails struct { Phase string Provider string KFDVersion string + DryRun bool } diff --git a/internal/analytics/tracker.go b/internal/analytics/tracker.go index 5ba612c59..8fe6c2a08 100644 --- a/internal/analytics/tracker.go +++ b/internal/analytics/tracker.go @@ -8,6 +8,7 @@ import ( "fmt" "net" "net/http" + "strings" "time" "github.com/denisbrodbeck/machineid" @@ -15,6 +16,8 @@ import ( "github.com/sirupsen/logrus" ) +const isNext = true + // NewTracker returns a new Tracker instance. func NewTracker(token, version, arch, os, org, hostname string) *Tracker { const timeout = time.Second * 5 @@ -31,6 +34,8 @@ func NewTracker(token, version, arch, os, org, hostname string) *Tracker { }, } + isDevelopBuild := strings.Contains(version, "develop") + t := map[string]string{ "version": version, "origin": "furyctl", @@ -39,6 +44,8 @@ func NewTracker(token, version, arch, os, org, hostname string) *Tracker { "org": org, "hostname": hostname, "trackID": getTrackID(token), + "isNext": fmt.Sprintf("%t", isNext), + "development": fmt.Sprintf("%t", isDevelopBuild), } tracker := &Tracker{ @@ -50,6 +57,8 @@ func NewTracker(token, version, arch, os, org, hostname string) *Tracker { if token == "" { tracker.enable = false + + return tracker } // Start the event processor, this will listen for new tracked events and send them to mixpanel. @@ -81,14 +90,16 @@ func (a *Tracker) Track(event Event) { func (a *Tracker) Flush() { const timeout = time.Millisecond * 500 - go func() { - time.Sleep(timeout) - a.events <- NewStopEvent() - }() + if a.enable { + go func() { + time.Sleep(timeout) + a.events <- NewStopEvent() + }() - a.processEvents() + a.processEvents() - logrus.Debug("Flushed events queue") + logrus.Debug("Flushed events queue") + } } // processEvents is the event processor: it will listen for new events and send them to mixpanel. diff --git a/main.go b/main.go index 79eeb2d1d..acfd83391 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ package main import ( "os" "runtime" + "strings" "github.com/sirupsen/logrus" @@ -66,8 +67,11 @@ func exec() int { h = "unknown" } + mixPanelToken = strings.ReplaceAll(mixPanelToken, "\"", "") + mixPanelToken = strings.ReplaceAll(mixPanelToken, "'", "") + // Create the analytics tracker. - a := analytics.NewTracker(mixPanelToken, versions[version], osArch, runtime.GOOS, "SIGHUP", h) + a := analytics.NewTracker(mixPanelToken, versions["version"], osArch, runtime.GOOS, "SIGHUP", h) defer a.Flush() From 149fcac6348ec0202c5fc41a6a08c7c67d932c22 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 14:03:54 +0200 Subject: [PATCH 374/383] feat: remove windows release --- .goreleaser.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index a798d538e..be09f0a54 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -13,7 +13,6 @@ builds: - MIX_PANEL_TOKEN={{ if index .Env "FURYCTL_MIXPANEL_TOKEN" }}{{ .Env.FURYCTL_MIXPANEL_TOKEN }}{{ else }}""{{ end }} goos: - linux - - windows - darwin goarch: - amd64 @@ -23,20 +22,20 @@ builds: archives: - name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}" checksum: - name_template: 'checksums.txt' + name_template: "checksums.txt" snapshot: name_template: "{{ incpatch .Version }}-develop" changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - "^docs:" + - "^test:" release: github: owner: sighupio name: furyctl - name_template: '{{ .Tag }}' + name_template: "{{ .Tag }}" prerelease: auto brews: - name: furyctl @@ -45,8 +44,8 @@ brews: name: homebrew-furyctl skip_upload: auto folder: Formula - homepage: 'https://gihub.com/sighupio/furyctl' - description: 'furyctl binary' + homepage: "https://gihub.com/sighupio/furyctl" + description: "furyctl binary" conflicts: - furyctl test: | From 627d8180d6098d6b07e409bd5787eea8ad0da031 Mon Sep 17 00:00:00 2001 From: Samuele Chiocca Date: Wed, 5 Jul 2023 15:58:16 +0200 Subject: [PATCH 375/383] chores: remove example folder, we already have the example configs from the distro --- examples/furyctl.yaml | 106 ------------------------------------------ 1 file changed, 106 deletions(-) delete mode 100644 examples/furyctl.yaml diff --git a/examples/furyctl.yaml b/examples/furyctl.yaml deleted file mode 100644 index 4904ede2c..000000000 --- a/examples/furyctl.yaml +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - - ---- -apiVersion: kfd.sighup.io/v1alpha2 -kind: EKSCluster -metadata: - name: example -spec: - distributionVersion: v1.25.2 - toolsConfiguration: - terraform: - state: - s3: - bucketName: example - keyPrefix: test/example - region: eu-west-1 - region: eu-west-1 - tags: - env: "test" - k8s: "example" - githubOrg: "acme" - githubRepo: "furyctl" - infrastructure: - vpc: - network: - cidr: 10.10.0.0/16 - subnetsCidrs: - private: - - 10.10.0.0/20 - - 10.10.16.0/20 - - 10.10.32.0/20 - - 10.10.48.0/20 - public: - - 10.10.192.0/24 - - 10.10.193.0/24 - - 10.10.194.0/24 - vpn: - instances: 1 - instanceType: t3.micro - diskSize: 50 - operatorName: acme - dhParamsBits: 2048 - vpnClientsSubnetCidr: 172.16.0.0/16 - ssh: - publicKeys: ["{file:////.ssh/id_rsa.pub}"] - githubUsersName: - - - allowedFromCidrs: - - 0.0.0.0/0 - kubernetes: - apiServer: - privateAccess: true - publicAccess: false - privateAccessCidrs: ["0.0.0.0/0"] - publicAccessCidrs: [] - nodeAllowedSshPublicKey: "{file:////.ssh/id_rsa.pub}" - nodePoolsLaunchKind: "launch_templates" - logRetentionDays: 1 - nodePools: - - name: infra - size: - min: 3 - max: 3 - instance: - type: t3.xlarge - spot: true - volumeSize: 50 - labels: - nodepool: infra - node.kubernetes.io/role: infra - taints: - - node.kubernetes.io/role=infra:NoSchedule - tags: - k8s.io/cluster-autoscaler/node-template/label/nodepool: "infra" - k8s.io/cluster-autoscaler/node-template/label/node.kubernetes.io/role: "infra" - k8s.io/cluster-autoscaler/node-template/taint/node.kubernetes.io/role: "infra:NoSchedule" - distribution: - modules: - ingress: - baseDomain: internal.example.dev - nginx: - type: dual - tls: - provider: certManager - certManager: - clusterIssuer: - name: letsencrypt-fury - email: fury@example.dev - type: http01 - dns: - public: - name: "example.dev" - create: true - private: - name: "internal.example.dev" - create: true - logging: - type: loki - dr: - velero: - eks: - bucketName: furyctl-example-qgcurpen - region: eu-west-1 From fdae37712f9a7a6d2afedb621c23aa8c94682c37 Mon Sep 17 00:00:00 2001 From: Samuele Chiocca Date: Wed, 5 Jul 2023 15:58:35 +0200 Subject: [PATCH 376/383] docs: update README to prepare the 0.25.0 release --- README.md | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b76b2f314..1f2093991 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ -> We are in the process of rewriting `furyctl` from the ground up. The new version is called `furyctl-ng` and is currently in `alpha` status, and will be released starting from version `v0.25.0-alpha.1`. The former version of `furyctl` will be enter 'bugfix only' maintenance until the new version is stable enough to replace it, and it will live under the old `v0.1x` branches. +> The next generation of `furyctl`, called "furyctl next", has been officially released. It is now in a stable state and available starting from version v0.25.0. The previous version, furyctl 0.11, is considered legacy and will only receive bug fixes. It will be maintained under the v0.11 branches. `furyctl` is the command line companion for the Kubernetes Fury Distribution to manage the **full lifecycle** of your Kubernetes Fury clusters.
@@ -50,16 +50,12 @@ You can find `furyctl` binaries on the [Releases page](https://github.com/sighup To download the latest release, run: ```console -curl -L "https://github.com/sighupio/furyctl/releases/download/v0.25.0-alpha.1/furyctl_$(uname -s)_x86_64.tar.gz" -o /tmp/furyctl.tar.gz && tar xfz /tmp/furyctl.tar.gz -C /tmp +curl -L "https://github.com/sighupio/furyctl/releases/download/v0.25.0/furyctl_$(uname -s)_x86_64.tar.gz" -o /tmp/furyctl.tar.gz && tar xfz /tmp/furyctl.tar.gz -C /tmp chmod +x /tmp/furyctl sudo mv /tmp/furyctl /usr/local/bin/furyctl ``` -Alternatively, you can install `furyctl` using a brew tap or via an asdf plugin. - -> ❗️**WARNING** -> -> M1 users: please download `darwin/amd64` binaries instead of using homebrew or asdf. Even though furyctl can be built for `arm64`, some of its dependendecies are not available yet for this architecture. +Alternatively, you can install `furyctl` using the asdf plugin. ### Installing with [asdf](https://github.com/asdf-vm/asdf) @@ -81,12 +78,11 @@ Check that everything is working correctly with `furyctl version`: ```console $ furyctl version -buildTime: 2023-01-13T09:50:15Z -gitCommit: 349c14a06dd6163b308e4e8baa47ec9cc59712e1 +... goVersion: go1.20 osArch: amd64 -version: 0.25.0-alpha.1 -``` --> +version: 0.25.0 +``` ### Installing from source @@ -112,8 +108,6 @@ Once you've ensured the above dependencies are installed, you can proceed with t git clone git@github.com:sighupio/furyctl.git # cd into the cloned repository cd furyctl -# Switch to the branch for the `furyctl-ng-alpha1` version -git switch furyctl-ng-alpha1 ``` 2. Build the binaries by running the following command: @@ -216,6 +210,8 @@ Basic usage of `furyctl` for a new project consists on the following steps: Furyctl configuration files have a kind that specifies what type of cluster will be created, for example the `EKSCluster` kind has all the parameters needed to create a KFD cluster using the EKS managed clusters from AWS. +You can also use the `KFDDistribution` kind to install the KFD distribution on top of an existing Kubernetes cluster. + Additionaly, the schema of the file is versioned with the `apiVersion` field, so when new features are introduced you can switch to a newer version of the configuration file structure. To scaffold a configuration file to use as a starter, you use the following command: @@ -224,8 +220,6 @@ To scaffold a configuration file to use as a starter, you use the following comm furyctl create config --version ``` -Alternatively, you can take a look at the one in the [examples folder](./examples/). - > 💡 **TIP** > > You can pass some additional flags, like the schema (API) version of the configuration file or a different configuration file name. @@ -311,6 +305,10 @@ Check that the dry-run output is what you expect and then run the command again ### Advanced Usage + #### Cluster creation @@ -382,6 +381,16 @@ to use the flag `--kubeconfig` in the following command. furyctl create cluster --phase distribution' ``` +#### Legacy download + +The new furyctl still embed some of the legacy features, for example the command `furyctl legacy vendor` to download KFD dependencies from a deprecated `Furyfile.yml`. + +This can be still used to manually manage all the components of the distribution. + +> 💡 **TIP** +> +> you can also use `--furyfile` to point to a `Furyfile.yml` in a different folder + ### Advanced Tips Furyctl comes with the flag `--distro-location`, allowing you to use a local copy of KFD instead of downloading it from the internet. This allows you to test changes to the KFD without having to push them to the repository, and might come in handy when you need to test new features or bugfixes. From e296f48c48034ea43c4ce7925c03a6182b77f5fe Mon Sep 17 00:00:00 2001 From: Samuele Chiocca Date: Wed, 5 Jul 2023 16:03:53 +0200 Subject: [PATCH 377/383] docs: add What is furyctl section --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f2093991..000455462 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ -> The next generation of `furyctl`, called "furyctl next", has been officially released. It is now in a stable state and available starting from version v0.25.0. The previous version, furyctl 0.11, is considered legacy and will only receive bug fixes. It will be maintained under the v0.11 branches. +> The next generation of `furyctl`, called "furyctl next", has been officially released. It is now in a stable state and available starting from version v0.25.0. The previous version, furyctl 0.11, is considered legacy and will only receive bug fixes. It will be maintained under the v0.11 branch. + +## What is Furyctl? `furyctl` is the command line companion for the Kubernetes Fury Distribution to manage the **full lifecycle** of your Kubernetes Fury clusters.
@@ -41,6 +43,11 @@ > > `furyctl-ng` is in `alpha` status and currently supports EKS-based clusters only. +### Available providers + +* `EKSCluster`: Provides comprehensive lifecycle management for an EKS cluster on AWS. It handles the installation of the VPC, VPN, EKS using the installers, and deploys the Distribution onto the EKS cluster. +* `KFDDistribution`: Dedicated provider for the distribution, which installs the Distribution (modules only) on an existing Kubernetes cluster. + ## Installation ### Installing from binaries From aae1e7ce3b74524e1734a73663ed6e899a187622 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 16:32:37 +0200 Subject: [PATCH 378/383] docs: change build status image --- README.md | 89 +++++++++++-------------------------------------------- 1 file changed, 18 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 000455462..0eefca6f2 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ -[![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg?ref=refs/heads/furyctl-ng-alpha1)](https://ci.sighup.io/sighupio/furyctl) -![Release](https://img.shields.io/badge/furyctl-v0.10.0-blue) -![Release](https://img.shields.io/badge/furyctl%20next-v0.25.0‐alpha.1-blue) +[![Build Status](https://ci.sighup.io/api/badges/sighupio/furyctl/status.svg)](https://ci.sighup.io/sighupio/furyctl) +![Release](https://img.shields.io/badge/furyctl-v0.25.0-blue) +![Release](https://img.shields.io/badge/furyctl%20next-v0.25.0-blue) ![Slack](https://img.shields.io/badge/slack-@kubernetes/fury-yellow.svg?logo=slack) ![License](https://img.shields.io/github/license/sighupio/furyctl) [![Go Report Card](https://goreportcard.com/badge/github.com/sighupio/furyctl)](https://goreportcard.com/report/github.com/sighupio/furyctl) @@ -27,26 +27,14 @@ `furyctl` is the command line companion for the Kubernetes Fury Distribution to manage the **full lifecycle** of your Kubernetes Fury clusters.
-> **:warning:** -> you are viewing the readme for furyctl next generation (`ng` for short). This version is in `alpha` status. -> -> `furyctl-ng` supports EKS-based clusters only in the first alpha. - - - > 💡 Learn more about the Kubernetes Fury Distribution in the [official site](https://kubernetesfury.com). - - -> **Warning** -> You are viewing the readme for furyctl next generation (`furyctl-ng` for short). This version is in `alpha` status. -> -> `furyctl-ng` is in `alpha` status and currently supports EKS-based clusters only. +If you're looking for the old documentation, you can find it [here](https://github.com/sighupio/furyctl/blob/release-v0.11/README.md). ### Available providers -* `EKSCluster`: Provides comprehensive lifecycle management for an EKS cluster on AWS. It handles the installation of the VPC, VPN, EKS using the installers, and deploys the Distribution onto the EKS cluster. -* `KFDDistribution`: Dedicated provider for the distribution, which installs the Distribution (modules only) on an existing Kubernetes cluster. +- `EKSCluster`: Provides comprehensive lifecycle management for an EKS cluster on AWS. It handles the installation of the VPC, VPN, EKS using the installers, and deploys the Distribution onto the EKS cluster. +- `KFDDistribution`: Dedicated provider for the distribution, which installs the Distribution (modules only) on an existing Kubernetes cluster. ## Installation @@ -109,7 +97,7 @@ Once you've ensured the above dependencies are installed, you can proceed with t 1. Clone the repository: - + ```console git clone git@github.com:sighupio/furyctl.git @@ -149,44 +137,6 @@ dist/furyctl_windows_amd64_v1 sudo mv ./dist/furyctl_darwin_amd64_v1/furyctl /usr/local/bin/furyctl ``` -### Installation from binaries (not available yet for `furyctl-ng`) - -You can find `furyctl` binaries on the [Releases page](https://github.com/sighupio/furyctl/releases). - -To download the latest release, run: - -```bash -wget -q "https://github.com/sighupio/furyctl/releases/download/v0.10.0/furyctl-$(uname -s)-amd64" -O /tmp/furyctl -chmod +x /tmp/furyctl -sudo mv /tmp/furyctl /usr/local/bin/furyctl -``` - -Alternatively, you can install `furyctl` using a brew tap or via an asdf plugin. - -> ⚠️ M1 users: please download `darwin/amd64` binaries instead of using homebrew or asdf. Even though furyctl can be build for `arm64`, some of its dependendecies are not available yet for this architecture. - -### Installation with [Homebrew](https://brew.sh/) (not available yet for `furyctl-ng`) - -```console -brew tap sighupio/furyctl -brew install furyctl -``` - -### Installation with [asdf](https://github.com/asdf-vm/asdf) (not available yet for `furyctl-ng`) - -Add furyctl asdf plugin: - -```console -asdf plugin add furyctl -``` - -Check that everything is working correctly with `furyctl version`: - -```bash -furyctl version -INFO[0000] Furyctl version 0.10.0 -``` - ## Usage See all the available commands and their usage by running `furyctl help`. @@ -198,10 +148,7 @@ See all the available commands and their usage by running `furyctl help`. -> ❗️**WARNING** -> -> `furyctl-ng` is compatible with KFD versions 1.22.1, 1.23.3 and 1.24.0, but you will need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` -> in _every command_ until the next release of the KFD. +> Check [KFD Compatibility matrix](https://github.com/sighupio/fury-distribution/blob/main/docs/COMPATIBILITY_MATRIX.md) for the Furyctl / KFD versions to use. ### Basic Usage @@ -224,7 +171,7 @@ Additionaly, the schema of the file is versioned with the `apiVersion` field, so To scaffold a configuration file to use as a starter, you use the following command: ```console -furyctl create config --version +furyctl create config --version v1.25.4 --kind "EKSCluster" ``` > 💡 **TIP** @@ -274,11 +221,15 @@ furyctl validate dependencies Last but not least, you can launch the creation of the resources defined in the configuration file by running the following command: -> **:warning:** you are about to create cloud resources that could have billing impact. +> ❗️ **WARNING** +> +> You are about to create cloud resources that could have billing impact. - + -> **:info:** the creation process you are about to launch can take a while. +> 📖 **NOTE** +> +> The cluster creation process, by default, will create a VPN in the `infrastructure` phase and connect your machine to it automatically before proceeding to the `kubernetes` phase. ```console furyctl create cluster --config /path/to/your/furyctl.yaml @@ -348,11 +299,7 @@ The following steps will guide you through the process of creating a Kubernetes 4. Run `furyctl create cluster` to create the cluster. 5. (Optional) Watch the logs of the cluster creation process with `tail -f ~/.furyctl/furyctl.log`. -> 💡 **Alpha ONLY** -> -> You may need to use the flag `--distro-location git::git@github.com:sighupio/fury-distribution.git?ref=feature/furyctl-next` until the next release of the KFD. - -### Deploy a cluster from an already existing infrastructure +#### Create a cluster in an already existing infrastructure Same as the previous section, but you can skip the infrastructure creation phase by not filling the section `infrastructure` in the `furyctl.yaml` file and @@ -367,7 +314,7 @@ The cluster creation process can be split into three phases: 3. Distribution The `furyctl create cluster` command will execute all the phases by default, -but you can limit the execution to a specific phase by using the flag `--phase`. +but you can limit the execution to a specific phase by using the `--phase` flag. To create a cluster step by step, you can run the following command: From 7f16a8ef2a18d92694fb7ba013613453f3c531e5 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 16:34:42 +0200 Subject: [PATCH 379/383] deps: update fury-distribution dependency --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fe98c66ee..94f1e17be 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/onsi/gomega v1.24.2 github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 github.com/shirou/gopsutil/v3 v3.23.2 - github.com/sighupio/fury-distribution v1.25.4-0.20230703100332-a87399c2785b + github.com/sighupio/fury-distribution v1.25.4 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 diff --git a/go.sum b/go.sum index af20489c6..1eb450e48 100644 --- a/go.sum +++ b/go.sum @@ -306,6 +306,8 @@ github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26 h1:k github.com/sighupio/fury-distribution v1.25.4-0.20230630151509-5fef14d7ef26/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= github.com/sighupio/fury-distribution v1.25.4-0.20230703100332-a87399c2785b h1:vH8RFn/gu5oNOdj1Rc9LnJDCmT+3aaveN4PyBbomTBE= github.com/sighupio/fury-distribution v1.25.4-0.20230703100332-a87399c2785b/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= +github.com/sighupio/fury-distribution v1.25.4 h1:Jrf0A6qIHY4obhFfl6WHufWhZl2Vc8QMF6q0D80P7zA= +github.com/sighupio/fury-distribution v1.25.4/go.mod h1:5gp8s+7qkpyoVeOZNuMRDLhjElYEY1F3X1GmC8QkYc0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= From 4fb86bbde3ce5a40c32c2e7359ecc8b1a301ccc3 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 17:30:23 +0200 Subject: [PATCH 380/383] fix: use correct default file --- cmd/dump/template.go | 1 + .../kfd/v1alpha2/eks/create/kubernetes.go | 2 +- internal/distribution/iac.go | 29 +++++++++++++++---- internal/distribution/path.go | 4 --- internal/distribution/path_test.go | 8 ----- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/cmd/dump/template.go b/cmd/dump/template.go index 86eb43e57..bdfac1164 100644 --- a/cmd/dump/template.go +++ b/cmd/dump/template.go @@ -108,6 +108,7 @@ The generated folder will be created starting from a provided templates folder a distroManBuilder, err := distribution.NewIACBuilder( furyctlFile, res.RepoPath, + res.MinimalConf.Kind, flags.OutDir, flags.NoOverwrite, flags.DryRun, diff --git a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go index 953cb9e84..5784152a0 100644 --- a/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go +++ b/internal/apis/kfd/v1alpha2/eks/create/kubernetes.go @@ -386,7 +386,7 @@ func (k *Kubernetes) copyFromTemplate(furyctlCfg template.Config) error { func (k *Kubernetes) mergeConfig() (template.Config, error) { var cfg template.Config - defaultsFilePath := path.Join(k.distroPath, "furyctl-defaults.yaml") + defaultsFilePath := path.Join(k.distroPath, "defaults", "ekscluster-kfd-v1alpha2.yaml") defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) if err != nil { diff --git a/internal/distribution/iac.go b/internal/distribution/iac.go index 078bd311e..c8ada4816 100644 --- a/internal/distribution/iac.go +++ b/internal/distribution/iac.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "os" + "path" "path/filepath" "github.com/sirupsen/logrus" @@ -18,12 +19,14 @@ import ( ) const ( - source = "templates/distribution" - defaultsFileName = "furyctl-defaults.yaml" - suffix = ".tpl" + source = "templates/distribution" + suffix = ".tpl" ) -var ErrSourceDirDoesNotExist = errors.New("source directory does not exist") +var ( + ErrSourceDirDoesNotExist = errors.New("source directory does not exist") + ErrInvalidKind = errors.New("invalid kind") +) type IACBuilder struct { furyctlFile map[any]any @@ -31,11 +34,13 @@ type IACBuilder struct { outDir string noOverwrite bool dryRun bool + kind string } func NewIACBuilder( furyctlFile map[any]any, distroPath, + kind, outDir string, noOverwrite, dryRun bool, @@ -51,6 +56,7 @@ func NewIACBuilder( outDir: absOutDir, noOverwrite: noOverwrite, dryRun: dryRun, + kind: kind, }, nil } @@ -138,7 +144,20 @@ func (m *IACBuilder) Build() error { } func (m *IACBuilder) defaultsFile() (map[any]any, error) { - defaultsFilePath := filepath.Join(m.distroPath, defaultsFileName) + var defaultsFileName string + + switch m.kind { + case "EKSCluster": + defaultsFileName = "ekscluster-kfd-v1alpha2.yaml" + + case "KFDDistribution": + defaultsFileName = "kfddistribution-kfd-v1alpha2.yaml" + + default: + return nil, ErrInvalidKind + } + + defaultsFilePath := path.Join("defaults", defaultsFileName) defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) if err != nil { diff --git a/internal/distribution/path.go b/internal/distribution/path.go index 10bab94e2..62129eb5d 100644 --- a/internal/distribution/path.go +++ b/internal/distribution/path.go @@ -32,10 +32,6 @@ func GetPrivateSchemaPath(basePath string, conf config.Furyctl) (string, error) return getPath(basePath, conf, "%s-%s-%s.json", "schemas/private") } -func GetDefaultsPath(basePath string) string { - return filepath.Join(basePath, "furyctl-defaults.yaml") -} - func getPath(basePath string, conf config.Furyctl, fnameTpl, subDir string) (string, error) { avp := strings.Split(conf.APIVersion, "/") diff --git a/internal/distribution/path_test.go b/internal/distribution/path_test.go index d83ae4f42..96d422ed2 100644 --- a/internal/distribution/path_test.go +++ b/internal/distribution/path_test.go @@ -176,11 +176,3 @@ func TestGetSchemaPaths(t *testing.T) { }) } } - -func TestGetDefaultsPath(t *testing.T) { - dp := distribution.GetDefaultsPath("/tmp") - - if dp != "/tmp/furyctl-defaults.yaml" { - t.Errorf("expected /tmp/furyctl-defaults.yaml, got %s", dp) - } -} From 883a61b62fd01920ce71e2dd77e0627af3e3cb11 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 17:53:44 +0200 Subject: [PATCH 381/383] fix: add paths --- internal/distribution/iac.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/distribution/iac.go b/internal/distribution/iac.go index c8ada4816..8840c979b 100644 --- a/internal/distribution/iac.go +++ b/internal/distribution/iac.go @@ -96,6 +96,12 @@ func (m *IACBuilder) Build() error { return fmt.Errorf("error creating template config: %w", err) } + tmplCfg.Data["paths"] = map[any]any{ + "kubectl": "", + "kustomize": "", + "yq": "", + } + outYaml, err := yamlx.MarshalV2(tmplCfg) if err != nil { return fmt.Errorf("error marshaling template config: %w", err) @@ -157,7 +163,7 @@ func (m *IACBuilder) defaultsFile() (map[any]any, error) { return nil, ErrInvalidKind } - defaultsFilePath := path.Join("defaults", defaultsFileName) + defaultsFilePath := path.Join(m.distroPath, "defaults", defaultsFileName) defaultsFile, err := yamlx.FromFileV2[map[any]any](defaultsFilePath) if err != nil { From 982b2c07c4969e55fb40aadeaa4e8d81b1787be0 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 18:04:14 +0200 Subject: [PATCH 382/383] fix: fix e2e tests --- .../ekscluster-kfd-v1alpha2.yaml} | 0 .../ekscluster-kfd-v1alpha2.yaml} | 0 .../defaults/ekscluster-kfd-v1alpha2.yaml | 192 ++++++++++++++++++ .../simple-dry-run/furyctl-defaults.yaml | 7 - .../defaults/ekscluster-kfd-v1alpha2.yaml | 192 ++++++++++++++++++ .../template/simple/furyctl-defaults.yaml | 7 - 6 files changed, 384 insertions(+), 14 deletions(-) rename test/data/e2e/dump/template/complex-dry-run/{furyctl-defaults.yaml => defaults/ekscluster-kfd-v1alpha2.yaml} (100%) rename test/data/e2e/dump/template/complex/{furyctl-defaults.yaml => defaults/ekscluster-kfd-v1alpha2.yaml} (100%) create mode 100644 test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml delete mode 100644 test/data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml create mode 100644 test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml delete mode 100644 test/data/e2e/dump/template/simple/furyctl-defaults.yaml diff --git a/test/data/e2e/dump/template/complex-dry-run/furyctl-defaults.yaml b/test/data/e2e/dump/template/complex-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml similarity index 100% rename from test/data/e2e/dump/template/complex-dry-run/furyctl-defaults.yaml rename to test/data/e2e/dump/template/complex-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml diff --git a/test/data/e2e/dump/template/complex/furyctl-defaults.yaml b/test/data/e2e/dump/template/complex/defaults/ekscluster-kfd-v1alpha2.yaml similarity index 100% rename from test/data/e2e/dump/template/complex/furyctl-defaults.yaml rename to test/data/e2e/dump/template/complex/defaults/ekscluster-kfd-v1alpha2.yaml diff --git a/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml b/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml new file mode 100644 index 000000000..997ccf89b --- /dev/null +++ b/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789123:role/clustername-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: {} + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storageSize: "150Gi" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: {} + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role + region: eu-west-1 + bucketName: velero-bucket + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: {} + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: 5n12jUeGL5Oy9zXiCOP929xc4sG2n2/CB9QTo2piNsU= + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: ausahceemoh3ahGhiu6aiNguothuVakuYah5Lie5 + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: xI1QpxLnGwreSJhnXRWNSthsdVLv6aZFX+Cwos5SCvY= + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ion-eks-demo + iamRoleArn: arn:aws:iam::363601582189:role/demo-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/test/data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml b/test/data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml deleted file mode 100644 index 493b3eb66..000000000 --- a/test/data/e2e/dump/template/simple-dry-run/furyctl-defaults.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -data: - test: - hello: defaultValue diff --git a/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml b/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml new file mode 100644 index 000000000..997ccf89b --- /dev/null +++ b/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml @@ -0,0 +1,192 @@ +# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +data: + # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. + common: + # where all the KFD modules are downloaded + relativeVendorPath: "../../vendor" + provider: + # can be eks for now, in the future we will add additional providers + type: eks + # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed + nodeSelector: + node.kubernetes.io/role: infra + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + value: infra + # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses + + # the module section will be used to fine tune each module behaviour and configuration + modules: + # ingress module configuration + ingress: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + forecastle: + # disable authentication if set globally on auth module + disableAuth: false + # if empty, will use the default packageName + baseDomain from common configurations + host: "" + ingressClass: "" + + baseDomain: internal.fury-demo.sighup.io + # common configuration for nginx ingress controller + nginx: + # can be single or dual + type: single + tls: + # can be certManager, secret or none + provider: certManager # it uses the configuration below as default when certManager is chosen + secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly + cert: | + value + key: | + value + ca: | + value + # the standard configuration for cert-manager on the ingress module + certManager: + # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity + clusterIssuer: + name: letsencrypt-fury + # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external + type: http01 + # if auth type is route53, we need to provide the following configurations + route53: + iamRoleArn: arn:aws:iam::123456789123:role/clustername-cert-manager + region: eu-west-1 + hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 + # logging module configuration + logging: + overrides: + nodeSelector: {} + tolerations: {} + ingresses: + opensearch-dashboards: + disableAuth: false + host: "" + ingressClass: "" + cerebro: + disableAuth: false + host: "" + ingressClass: "" + opensearch: + # can be single or triple + type: single + # if not set, no resource patch will be created + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # if set, it will override the volumeClaimTemplates in the opensearch statefulSet + storageSize: "150Gi" + # override ingresses parameters + # monitoring module configuration + monitoring: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + prometheus: + disableAuth: false + host: "" + ingressClass: "" + alertmanager: + disableAuth: false + host: "" + ingressClass: "" + grafana: + disableAuth: false + host: "" + ingressClass: "" + goldpinger: + disableAuth: false + host: "" + ingressClass: "" + prometheus: + resources: + requests: + cpu: "" + memory: "" + limits: + cpu: "" + memory: "" + # policy module configuration + policy: + overrides: + nodeSelector: {} + tolerations: {} + # override ingresses parameters + ingresses: + gpm: + disableAuth: false + host: "" + ingressClass: "" + # the standard configuration for gatekeeper on the policy module + gatekeeper: + # this configuration adds namespaces to the excluded list, actually whitelisting them + additionalExcludedNamespaces: [] + # dr module configuration + dr: + overrides: + nodeSelector: {} + tolerations: {} + # the standard configuration for velero on the dr module + velero: + # this configuration will be used if common.provider.type is eks + eks: + iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role + region: eu-west-1 + bucketName: velero-bucket + # auth module configuration + auth: + overrides: + nodeSelector: {} + # override ingresses parameters + ingresses: + pomerium: + # disableAuth: false <- This doesn't make sense here. + host: "" + ingressClass: "" + dex: + host: "" + ingressClass: "" + tolerations: {} + provider: + # can be none, basicAuth or sso. SSO uses pomerium+dex + type: none + basicAuth: + username: admin + password: admin + pomerium: + secrets: + # override environment variables here + ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret + COOKIE_SECRET: 5n12jUeGL5Oy9zXiCOP929xc4sG2n2/CB9QTo2piNsU= + ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client + IDP_CLIENT_SECRET: ausahceemoh3ahGhiu6aiNguothuVakuYah5Lie5 + ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret + SHARED_SECRET: xI1QpxLnGwreSJhnXRWNSthsdVLv6aZFX+Cwos5SCvY= + dex: + # see dex documentation for more information + connectors: [] + aws: + clusterAutoscaler: + nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ion-eks-demo + iamRoleArn: arn:aws:iam::363601582189:role/demo-cluster-autoscaler +templates: + includes: + - ".*\\.yaml" + - ".*\\.yml" + suffix: ".tpl" + processFilename: true diff --git a/test/data/e2e/dump/template/simple/furyctl-defaults.yaml b/test/data/e2e/dump/template/simple/furyctl-defaults.yaml deleted file mode 100644 index 493b3eb66..000000000 --- a/test/data/e2e/dump/template/simple/furyctl-defaults.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -data: - test: - hello: defaultValue From 46f0a640494334c5b82bcb4fea4ef5077e836ae4 Mon Sep 17 00:00:00 2001 From: Alessio Dionisi Date: Wed, 5 Jul 2023 18:15:27 +0200 Subject: [PATCH 383/383] fix: fix e2e tests again --- .../ekscluster-kfd-v1alpha2.yaml} | 0 .../ekscluster-kfd-v1alpha2.yaml} | 0 .../furyctl.yaml | 1 + .../defaults/ekscluster-kfd-v1alpha2.yaml | 189 +----------------- .../dump/template/simple-dry-run/furyctl.yaml | 1 + .../defaults/ekscluster-kfd-v1alpha2.yaml | 189 +----------------- .../e2e/dump/template/simple/furyctl.yaml | 1 + 7 files changed, 7 insertions(+), 374 deletions(-) rename test/data/e2e/create/cluster/kubernetes/data/{furyctl-defaults.yaml => defaults/ekscluster-kfd-v1alpha2.yaml} (100%) rename test/data/e2e/dump/template/distribution-yaml-no-data-property/{furyctl-defaults.yaml => defaults/ekscluster-kfd-v1alpha2.yaml} (100%) diff --git a/test/data/e2e/create/cluster/kubernetes/data/furyctl-defaults.yaml b/test/data/e2e/create/cluster/kubernetes/data/defaults/ekscluster-kfd-v1alpha2.yaml similarity index 100% rename from test/data/e2e/create/cluster/kubernetes/data/furyctl-defaults.yaml rename to test/data/e2e/create/cluster/kubernetes/data/defaults/ekscluster-kfd-v1alpha2.yaml diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl-defaults.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/defaults/ekscluster-kfd-v1alpha2.yaml similarity index 100% rename from test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl-defaults.yaml rename to test/data/e2e/dump/template/distribution-yaml-no-data-property/defaults/ekscluster-kfd-v1alpha2.yaml diff --git a/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl.yaml b/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl.yaml index 246e7604f..747682bcf 100644 --- a/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl.yaml +++ b/test/data/e2e/dump/template/distribution-yaml-no-data-property/furyctl.yaml @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +kind: EKSCluster spec: distribution: test: diff --git a/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml b/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml index 997ccf89b..493b3eb66 100644 --- a/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml +++ b/test/data/e2e/dump/template/simple-dry-run/defaults/ekscluster-kfd-v1alpha2.yaml @@ -3,190 +3,5 @@ # license that can be found in the LICENSE file. data: - # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. - common: - # where all the KFD modules are downloaded - relativeVendorPath: "../../vendor" - provider: - # can be eks for now, in the future we will add additional providers - type: eks - # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed - nodeSelector: - node.kubernetes.io/role: infra - tolerations: - - effect: NoSchedule - key: node.kubernetes.io/role - value: infra - # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses - - # the module section will be used to fine tune each module behaviour and configuration - modules: - # ingress module configuration - ingress: - overrides: - nodeSelector: {} - tolerations: {} - # override ingresses parameters - ingresses: - forecastle: - # disable authentication if set globally on auth module - disableAuth: false - # if empty, will use the default packageName + baseDomain from common configurations - host: "" - ingressClass: "" - - baseDomain: internal.fury-demo.sighup.io - # common configuration for nginx ingress controller - nginx: - # can be single or dual - type: single - tls: - # can be certManager, secret or none - provider: certManager # it uses the configuration below as default when certManager is chosen - secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly - cert: | - value - key: | - value - ca: | - value - # the standard configuration for cert-manager on the ingress module - certManager: - # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity - clusterIssuer: - name: letsencrypt-fury - # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external - type: http01 - # if auth type is route53, we need to provide the following configurations - route53: - iamRoleArn: arn:aws:iam::123456789123:role/clustername-cert-manager - region: eu-west-1 - hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 - # logging module configuration - logging: - overrides: - nodeSelector: {} - tolerations: {} - ingresses: - opensearch-dashboards: - disableAuth: false - host: "" - ingressClass: "" - cerebro: - disableAuth: false - host: "" - ingressClass: "" - opensearch: - # can be single or triple - type: single - # if not set, no resource patch will be created - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" - # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storageSize: "150Gi" - # override ingresses parameters - # monitoring module configuration - monitoring: - overrides: - nodeSelector: {} - tolerations: {} - # override ingresses parameters - ingresses: - prometheus: - disableAuth: false - host: "" - ingressClass: "" - alertmanager: - disableAuth: false - host: "" - ingressClass: "" - grafana: - disableAuth: false - host: "" - ingressClass: "" - goldpinger: - disableAuth: false - host: "" - ingressClass: "" - prometheus: - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" - # policy module configuration - policy: - overrides: - nodeSelector: {} - tolerations: {} - # override ingresses parameters - ingresses: - gpm: - disableAuth: false - host: "" - ingressClass: "" - # the standard configuration for gatekeeper on the policy module - gatekeeper: - # this configuration adds namespaces to the excluded list, actually whitelisting them - additionalExcludedNamespaces: [] - # dr module configuration - dr: - overrides: - nodeSelector: {} - tolerations: {} - # the standard configuration for velero on the dr module - velero: - # this configuration will be used if common.provider.type is eks - eks: - iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role - region: eu-west-1 - bucketName: velero-bucket - # auth module configuration - auth: - overrides: - nodeSelector: {} - # override ingresses parameters - ingresses: - pomerium: - # disableAuth: false <- This doesn't make sense here. - host: "" - ingressClass: "" - dex: - host: "" - ingressClass: "" - tolerations: {} - provider: - # can be none, basicAuth or sso. SSO uses pomerium+dex - type: none - basicAuth: - username: admin - password: admin - pomerium: - secrets: - # override environment variables here - ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret - COOKIE_SECRET: 5n12jUeGL5Oy9zXiCOP929xc4sG2n2/CB9QTo2piNsU= - ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client - IDP_CLIENT_SECRET: ausahceemoh3ahGhiu6aiNguothuVakuYah5Lie5 - ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret - SHARED_SECRET: xI1QpxLnGwreSJhnXRWNSthsdVLv6aZFX+Cwos5SCvY= - dex: - # see dex documentation for more information - connectors: [] - aws: - clusterAutoscaler: - nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ion-eks-demo - iamRoleArn: arn:aws:iam::363601582189:role/demo-cluster-autoscaler -templates: - includes: - - ".*\\.yaml" - - ".*\\.yml" - suffix: ".tpl" - processFilename: true + test: + hello: defaultValue diff --git a/test/data/e2e/dump/template/simple-dry-run/furyctl.yaml b/test/data/e2e/dump/template/simple-dry-run/furyctl.yaml index 246e7604f..747682bcf 100644 --- a/test/data/e2e/dump/template/simple-dry-run/furyctl.yaml +++ b/test/data/e2e/dump/template/simple-dry-run/furyctl.yaml @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +kind: EKSCluster spec: distribution: test: diff --git a/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml b/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml index 997ccf89b..493b3eb66 100644 --- a/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml +++ b/test/data/e2e/dump/template/simple/defaults/ekscluster-kfd-v1alpha2.yaml @@ -3,190 +3,5 @@ # license that can be found in the LICENSE file. data: - # the common section will be used by all the templates in all modules, everything defined here is something used by all the KFD modules. - common: - # where all the KFD modules are downloaded - relativeVendorPath: "../../vendor" - provider: - # can be eks for now, in the future we will add additional providers - type: eks - # nodeSelector and tolerations will be used to select the nodes where the KFD will be installed - nodeSelector: - node.kubernetes.io/role: infra - tolerations: - - effect: NoSchedule - key: node.kubernetes.io/role - value: infra - # this section will contain the authentication configuration that will be used to protect the KFD infrastructural ingresses - - # the module section will be used to fine tune each module behaviour and configuration - modules: - # ingress module configuration - ingress: - overrides: - nodeSelector: {} - tolerations: {} - # override ingresses parameters - ingresses: - forecastle: - # disable authentication if set globally on auth module - disableAuth: false - # if empty, will use the default packageName + baseDomain from common configurations - host: "" - ingressClass: "" - - baseDomain: internal.fury-demo.sighup.io - # common configuration for nginx ingress controller - nginx: - # can be single or dual - type: single - tls: - # can be certManager, secret or none - provider: certManager # it uses the configuration below as default when certManager is chosen - secret: #if we want to use custom certificates, the template should create a secret and set it as default certificate in NGINX, so patch nginx deployment accordingly - cert: | - value - key: | - value - ca: | - value - # the standard configuration for cert-manager on the ingress module - certManager: - # to create the clusterIssuer, this is an additional clusterIssuer than the two provided by cert-manager, for simplicity - clusterIssuer: - name: letsencrypt-fury - # can be route53 or http01 , if http01 we need to extrapolate the ingress type. If single the class will be nginx, if dual the class will be external - type: http01 - # if auth type is route53, we need to provide the following configurations - route53: - iamRoleArn: arn:aws:iam::123456789123:role/clustername-cert-manager - region: eu-west-1 - hostedZoneId: ZXXXXXXXXXXXXXXXXXXX0 - # logging module configuration - logging: - overrides: - nodeSelector: {} - tolerations: {} - ingresses: - opensearch-dashboards: - disableAuth: false - host: "" - ingressClass: "" - cerebro: - disableAuth: false - host: "" - ingressClass: "" - opensearch: - # can be single or triple - type: single - # if not set, no resource patch will be created - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" - # if set, it will override the volumeClaimTemplates in the opensearch statefulSet - storageSize: "150Gi" - # override ingresses parameters - # monitoring module configuration - monitoring: - overrides: - nodeSelector: {} - tolerations: {} - # override ingresses parameters - ingresses: - prometheus: - disableAuth: false - host: "" - ingressClass: "" - alertmanager: - disableAuth: false - host: "" - ingressClass: "" - grafana: - disableAuth: false - host: "" - ingressClass: "" - goldpinger: - disableAuth: false - host: "" - ingressClass: "" - prometheus: - resources: - requests: - cpu: "" - memory: "" - limits: - cpu: "" - memory: "" - # policy module configuration - policy: - overrides: - nodeSelector: {} - tolerations: {} - # override ingresses parameters - ingresses: - gpm: - disableAuth: false - host: "" - ingressClass: "" - # the standard configuration for gatekeeper on the policy module - gatekeeper: - # this configuration adds namespaces to the excluded list, actually whitelisting them - additionalExcludedNamespaces: [] - # dr module configuration - dr: - overrides: - nodeSelector: {} - tolerations: {} - # the standard configuration for velero on the dr module - velero: - # this configuration will be used if common.provider.type is eks - eks: - iamRoleArn: arn:aws:iam::123456789123:role/clustename-velero-velero-role - region: eu-west-1 - bucketName: velero-bucket - # auth module configuration - auth: - overrides: - nodeSelector: {} - # override ingresses parameters - ingresses: - pomerium: - # disableAuth: false <- This doesn't make sense here. - host: "" - ingressClass: "" - dex: - host: "" - ingressClass: "" - tolerations: {} - provider: - # can be none, basicAuth or sso. SSO uses pomerium+dex - type: none - basicAuth: - username: admin - password: admin - pomerium: - secrets: - # override environment variables here - ##COOKIE_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#cookie-secret - COOKIE_SECRET: 5n12jUeGL5Oy9zXiCOP929xc4sG2n2/CB9QTo2piNsU= - ##IDP_CLIENT_SECRET is the secret configured in the pomerium Dex static client - IDP_CLIENT_SECRET: ausahceemoh3ahGhiu6aiNguothuVakuYah5Lie5 - ##SHARED_SECRET is obtained with `head -c32 /dev/urandom | base64` see https://www.pomerium.io/reference/#shared-secret - SHARED_SECRET: xI1QpxLnGwreSJhnXRWNSthsdVLv6aZFX+Cwos5SCvY= - dex: - # see dex documentation for more information - connectors: [] - aws: - clusterAutoscaler: - nodeGroupAutoDiscovery: asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ion-eks-demo - iamRoleArn: arn:aws:iam::363601582189:role/demo-cluster-autoscaler -templates: - includes: - - ".*\\.yaml" - - ".*\\.yml" - suffix: ".tpl" - processFilename: true + test: + hello: defaultValue diff --git a/test/data/e2e/dump/template/simple/furyctl.yaml b/test/data/e2e/dump/template/simple/furyctl.yaml index 246e7604f..747682bcf 100644 --- a/test/data/e2e/dump/template/simple/furyctl.yaml +++ b/test/data/e2e/dump/template/simple/furyctl.yaml @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +kind: EKSCluster spec: distribution: test: