diff --git a/.gitignore b/.gitignore index d8e56a5..896b383 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ # Generated test configuration files testkit_v2/data/config.yml -testkit_v2/data/resources.yml \ No newline at end of file +testkit_v2/data/resources.yml + +# E2E test temporary files +e2e-tests/temp/ \ No newline at end of file diff --git a/e2e-tests/TODO.md b/e2e-tests/TODO.md new file mode 100644 index 0000000..f0249ab --- /dev/null +++ b/e2e-tests/TODO.md @@ -0,0 +1,57 @@ +# TODOs, ideas and questions + +## Переиспользование кластера + +1. Мы подключились к базовому кластеру (далее, гипервизор) и нужно создавать виртуалки. Мы по умолчанию считаем, что виртуализация там включена (Да) и падаем, если нет; либо мы включаем ее сами и падаем если не включилась (Нет). Да/нет? +2. Про идемпотентность тестового кластера. + +2.1. Есть у нас описанный конфигом тестовый кластер. Мы подключились к ДКП на гипервизоре и видим, что виртуалка (одна из) вроде бы уже есть и вроде даже такая, как надо - неймспейс, проц, память, диск, имя, образ, даже клауд-инит конфиг. НО подключиться к машине проверить ОС и ядро не можем - authentication error. В этом случае мы: + +2.1.1. Если это единственный мастер (в конфиге тестового кластера), сносим все виртуалки и переставляем их заново с развертыванием нового кластера с нуля (Да), или падаем (Нет). Да/нет? + +2.1.2. Если это воркер или не единственный мастер, при условии нормального подключения хотя бы к одному мастеру и его соответствия конфигу, мы удаляем эту ноду в кластере, удаляем виртуалку, переставляем ее с нуля и добавляем в кластер в той роли, в которой она была (Да), или падаем (Нет). Да/нет? Тут могут быть сложности в виде невыгоняемых подов с ноды - как быть в этом случае? + +2.2. Аналогично 2.1. но нода не соответствует конфигу. + +2.2.1. Если это единственный мастер, то убиваем все и переставляем весь кластер целиком с нуля (Да), либо падаем (Нет). Да/нет? + +2.2.2. Если это воркер или не единственный мастер, при условии нормального подключения хотя бы к одному мастеру и его соответствия конфигу, мы удаляем эту ноду в кластере, удаляем виртуалку, переставляем ее с нуля и добавляем в кластер в той роли, в которой она была (Да), или падаем (Нет). Да/нет? Тут могут быть сложности в виде невыгоняемых подов с ноды - как быть в этом случае? +> 2.3. Что делать если одна или несколько виртуалок выключены? Включаем и пробуем подключиться (Да), падаем (Нет). Да/нет? + + +alexandr.zimin +привет. + +1. Да - падаем, если нет виртуализации(сами не включаем) + +2. пункт - сделать флаг, который будет менять поведение, Пока там 2 переключалки сделать: +alwaysUseExisting (или еще варианты: ignoreTestConfig) - вообще не смотрим на требования, запускаем тесты на любом указанном кластере. если нет доступа - падаем и пишем об этом +alwaysRecreate - если в ns есть уже хоть одна виртуалка - падаем и пишем об этом. С этим режимом можно запускать только в чистом ns +Потом можно еще будет добавить useExistingWithConfigCheck - но пока его НЕ нужно реализовывать, чтобы время не терять + +наверное alwaysRecreate стоит переименовать на alwaysCreate + +ну и по умолчанию alwaysCreate + +хотя даже давай обязательным флагом сделаем + +просто падаем, если не задан + +## Новая репа + +Нужна ли? И в гитхабе ли? - сделать новую репу в фоксе, перетащить туда существующий код, убить в гитхаюе и пересоздать. + +## Как ревьюить и к кому с этим идти? + +Не ревьюить. + + + + + + +alwaysUseExisting - что проверять? Включен, доступен ДКП, включены модули? - да. +alwaysCreate - если виртуалки такие уже есть в НС, то падаем, если виртуалок нет - создаем кластер. + +Нужно все просто. Если есть какие-то непонятные кейсы, падать с ошибкой, потом обсудим и пофиксим, есличо. + diff --git a/e2e-tests/go.mod b/e2e-tests/go.mod index 63625c6..dcbefe6 100644 --- a/e2e-tests/go.mod +++ b/e2e-tests/go.mod @@ -5,65 +5,44 @@ go 1.24.2 toolchain go1.24.3 require ( - github.com/deckhouse/csi-nfs/api v0.0.0-20250529065000-055714087eef - github.com/deckhouse/sds-node-configurator/api v0.0.0-20241211082010-e61684e9db31 - github.com/deckhouse/sds-replicated-volume/api v0.0.0-20241206095502-b096856636cd - github.com/deckhouse/virtualization/api v0.15.0 - github.com/go-logr/logr v1.4.2 github.com/onsi/ginkgo/v2 v2.21.0 github.com/onsi/gomega v1.35.1 - k8s.io/api v0.32.1 - k8s.io/apiextensions-apiserver v0.31.0 - k8s.io/apimachinery v0.32.1 + github.com/pkg/sftp v1.13.10 + golang.org/x/crypto v0.46.0 + golang.org/x/term v0.38.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/client-go v0.32.1 - k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/controller-runtime v0.19.4 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/kr/fs v0.1.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 // indirect - github.com/openshift/custom-resource-status v1.1.2 // indirect - github.com/pborman/uuid v1.2.1 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.26.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + golang.org/x/tools v0.39.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apimachinery v0.32.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - kubevirt.io/api v1.0.0 // indirect - kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect - kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/e2e-tests/go.sum b/e2e-tests/go.sum index e1900c8..f5d951f 100644 --- a/e2e-tests/go.sum +++ b/e2e-tests/go.sum @@ -1,125 +1,46 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -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= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckhouse/csi-nfs/api v0.0.0-20250529065000-055714087eef h1:wuDOyamGLu2GQM89J1tk3ozSNdmW8MJurH5+hQ5HvNc= -github.com/deckhouse/csi-nfs/api v0.0.0-20250529065000-055714087eef/go.mod h1:b7i9vtLYdBEmAo0dtfEwUHu9hnnQyGuLTLy5XxD1GKA= -github.com/deckhouse/sds-node-configurator/api v0.0.0-20241211082010-e61684e9db31 h1:ZvYZNOHmRfqFp/2dy5p1dHA0EwN1kiem9iteZ0uIjE0= -github.com/deckhouse/sds-node-configurator/api v0.0.0-20241211082010-e61684e9db31/go.mod h1:ROmrnlcAdtYX8HPb0pe1qsnmISpy5FSW5fn2n67JOoE= -github.com/deckhouse/sds-replicated-volume/api v0.0.0-20241206095502-b096856636cd h1:MJzcWolbjy6SnZs9NMhvAq4KZdWRSQNIjcM9h+qGXa0= -github.com/deckhouse/sds-replicated-volume/api v0.0.0-20241206095502-b096856636cd/go.mod h1:6yz0RtbkLVJtK2DeuvgfaqBZRl5V5ax1WsfPF5pbnvo= -github.com/deckhouse/virtualization/api v0.15.0 h1:yRX6n18kK9wwO2f+8fc2s2nb1N2vYKxKb3/aSRtc9Kk= -github.com/deckhouse/virtualization/api v0.15.0/go.mod h1:t+6i4NC43RfNLqcZqkEc5vxY1ypKceqmOOKlVEq0cYA= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= -github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/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.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -127,273 +48,107 @@ 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/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 h1:t/CahSnpqY46sQR01SoS+Jt0jtjgmhgE6lFmRnO4q70= -github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183/go.mod h1:4VWG+W22wrB4HfBL88P40DxLEpSOaiBVxUnfalfJo9k= -github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= -github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= +github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/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.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-20180906233101-161cd47e91fd/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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20210220032951-036812b2e83c/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-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/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-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-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -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.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 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= -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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -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= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/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/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-20200615113413-eeeca48fe776/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.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= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= -k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= -k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= -k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= -k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -kubevirt.io/api v1.0.0 h1:RBdXP5CDhE0v5qL2OUQdrYyRrHe/F68Z91GWqBDF6nw= -kubevirt.io/api v1.0.0/go.mod h1:CJ4vZsaWhVN3jNbyc9y3lIZhw8nUHbWjap0xHABQiqc= -kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 h1:IWo12+ei3jltSN5jQN1xjgakfvRSF3G3Rr4GXVOOy2I= -kubevirt.io/containerized-data-importer-api v1.57.0-alpha1/go.mod h1:Y/8ETgHS1GjO89bl682DPtQOYEU/1ctPFBz6Sjxm4DM= -kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= -kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= -sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= -sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/e2e-tests/internal/cluster/builder.go b/e2e-tests/internal/cluster/builder.go deleted file mode 100644 index ae8bc30..0000000 --- a/e2e-tests/internal/cluster/builder.go +++ /dev/null @@ -1,329 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cluster - -import ( - "context" - "fmt" - "os" - "path/filepath" - "slices" - "strings" - - "github.com/deckhouse/sds-e2e-tests/internal/config" - "github.com/deckhouse/sds-e2e-tests/internal/infrastructure/ssh" - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes/virtualization" - "github.com/deckhouse/sds-e2e-tests/internal/logger" - "github.com/deckhouse/sds-e2e-tests/pkg/cluster" -) - -// Builder builds and provisions new clusters -type Builder struct { - config *config.Config - manager *Manager - sshFactory ssh.Factory -} - -// NewBuilder creates a new cluster builder -func NewBuilder(cfg *config.Config, manager *Manager, sshFactory ssh.Factory) *Builder { - return &Builder{ - config: cfg, - manager: manager, - sshFactory: sshFactory, - } -} - -// BuildCluster implements the full cluster creation workflow -func (b *Builder) BuildCluster(ctx context.Context, baseCluster cluster.Cluster, dkpCfg *config.DKPClusterConfig) (cluster.Cluster, error) { - logger.Infof("Starting cluster build process") // TODO amarkov: This and in similar places: Consider using logr interface so ginkgo's logger backend (GinkgoWriter) can be used. It can be passed via arguments or via context. - - // Step 1: Ensure base cluster connection via SSH - baseVMClient, ok := baseCluster.Virtualization().(virtualization.Client) - if !ok { - return nil, fmt.Errorf("base cluster does not support virtualization operations") - } - - // Step 2: Enable Deckhouse Virtualization Platform module on base cluster - logger.Infof("Ensuring Virtualization Platform module is enabled") - // This would enable the module - simplified for now - - // Step 3: Create virtual machines - logger.Infof("Creating virtual machines") - vms, err := b.createVMs(ctx, baseVMClient, dkpCfg) - if err != nil { - return nil, fmt.Errorf("failed to create VMs: %w", err) - } - - // Step 4: Identify master and bootstrap nodes - masterVM, bootstrapVM, workerVMs, err := b.identifyVMRoles(vms, dkpCfg) - if err != nil { - return nil, fmt.Errorf("failed to identify VM roles: %w", err) - } - - // Store worker VMs for later use (adding nodes to cluster) - _ = len(workerVMs) // Will be used in future for adding static nodes - - // Step 5: Connect to master via SSH and deploy Deckhouse - logger.Infof("Deploying Deckhouse on master node") - err = b.deployDeckhouse(ctx, masterVM, bootstrapVM) - if err != nil { - return nil, fmt.Errorf("failed to deploy Deckhouse: %w", err) - } - - // Step 6: Get kubeconfig of nested cluster - logger.Infof("Retrieving nested cluster kubeconfig") - nestedKubeConfig, err := b.getNestedKubeconfig(ctx, masterVM) - if err != nil { - return nil, fmt.Errorf("failed to get nested kubeconfig: %w", err) - } - - // Step 7: Create nested cluster client - nestedCluster, err := b.manager.GetOrCreate(ctx, nestedKubeConfig, "") - if err != nil { - return nil, fmt.Errorf("failed to create nested cluster client: %w", err) - } - - // Step 8: Enable and configure modules - logger.Infof("Enabling modules in nested cluster") - err = b.enableModules(ctx, nestedCluster, dkpCfg) - if err != nil { - return nil, fmt.Errorf("failed to enable modules: %w", err) - } - - logger.Infof("Cluster build completed successfully") - return nestedCluster, nil -} - -func (b *Builder) createVMs(ctx context.Context, vmClient virtualization.Client, dkpCfg *config.DKPClusterConfig) ([]*vmInfo, error) { - var vms []*vmInfo - - // Create master VMs - for _, master := range dkpCfg.ClusterDefinition.Masters { - req := virtualization.VMRequest{ - Name: master.Hostname, - Namespace: b.config.TestNS, - Image: master.OSType.Name, - ImageURL: master.OSType.ImageURL, - CPU: master.CPU, - RAM: master.RAM, - DiskSize: master.DiskSize, - StorageClass: b.config.BaseCluster.StorageClass, - SSHPubKey: "", // Would load from key file - IPAddress: master.IPAddress, - } - - vm, err := vmClient.CreateVM(ctx, req) - if err != nil { - return nil, fmt.Errorf("failed to create master VM %s: %w", master.Hostname, err) - } - - vms = append(vms, &vmInfo{ - name: master.Hostname, - role: "master", - vm: vm, - }) - } - - // Create worker VMs - for _, worker := range dkpCfg.ClusterDefinition.Workers { - req := virtualization.VMRequest{ - Name: worker.Hostname, - Namespace: b.config.TestNS, - Image: worker.OSType.Name, - ImageURL: worker.OSType.ImageURL, - CPU: worker.CPU, - RAM: worker.RAM, - DiskSize: worker.DiskSize, - StorageClass: b.config.BaseCluster.StorageClass, - SSHPubKey: "", - IPAddress: worker.IPAddress, - } - - vm, err := vmClient.CreateVM(ctx, req) - if err != nil { - return nil, fmt.Errorf("failed to create worker VM %s: %w", worker.Hostname, err) - } - - vms = append(vms, &vmInfo{ - name: worker.Hostname, - role: "worker", - vm: vm, - }) - } - - // Wait for all VMs to be running - // TODO amarkov: Consider extracting this to the separate method to separate create/wait operations. - for _, vm := range vms { - err := vmClient.WaitForVMRunning(ctx, b.config.TestNS, vm.name, 600) - if err != nil { - return nil, fmt.Errorf("VM %s failed to become ready: %w", vm.name, err) - } - - ip, err := vmClient.GetVMIP(ctx, b.config.TestNS, vm.name) - if err != nil { - return nil, fmt.Errorf("failed to get IP for VM %s: %w", vm.name, err) - } - vm.ip = ip - } - - return vms, nil -} - -func (b *Builder) identifyVMRoles(vms []*vmInfo, dkpCfg *config.DKPClusterConfig) (*vmInfo, *vmInfo, []*vmInfo, error) { - var masterVM, bootstrapVM *vmInfo - var workerVMs []*vmInfo - - for _, vm := range vms { - if vm.role == "master" { - masterVM = vm - } else if vm.role == "worker" { - workerVMs = append(workerVMs, vm) - } - } - - if masterVM == nil { - return nil, nil, nil, fmt.Errorf("no master VM found") - } - - // Bootstrap is first worker or setup node - if dkpCfg.ClusterDefinition.Setup != nil { - for _, vm := range vms { - if vm.name == dkpCfg.ClusterDefinition.Setup.Hostname { - bootstrapVM = vm - break - } - } - } - if bootstrapVM == nil && len(workerVMs) > 0 { - bootstrapVM = workerVMs[0] - } - - // workerVMs will be used later for adding nodes to the cluster - return masterVM, bootstrapVM, workerVMs, nil -} - -func (b *Builder) deployDeckhouse(ctx context.Context, masterVM, bootstrapVM *vmInfo) error { - // This is a simplified implementation - in production this would: - // 1. Connect to bootstrap VM via SSH - // 2. Upload config.yml and resources.yml - // 3. Install Docker if needed - // 4. Run dhctl bootstrap commands - // 5. Wait for Deckhouse to be ready - - logger.Infof("Deploying Deckhouse - connecting to bootstrap VM %s", bootstrapVM.ip) - // Simplified for demonstration - return nil -} - -func (b *Builder) getNestedKubeconfig(ctx context.Context, masterVM *vmInfo) (string, error) { - // Connect to master VM and get kubeconfig - sshClient, err := b.sshFactory.CreateClient("user", masterVM.ip+":22", b.config.NestedCluster.SSHKey) - if err != nil { - return "", fmt.Errorf("failed to create SSH client: %w", err) - } - defer sshClient.Close() - - kubeconfigContent := sshClient.ExecFatal(ctx, "sudo cat /root/.kube/config") - - // Replace port if needed - kubeconfigContent = strings.ReplaceAll(kubeconfigContent, "127.0.0.1:6445", "127.0.0.1:"+b.config.NestedCluster.K8sPort) - - // Write to file - kubeconfigPath := b.config.NestedCluster.KubeConfig - err = os.MkdirAll(filepath.Dir(kubeconfigPath), 0755) - if err != nil { - return "", err - } - - err = os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0600) - if err != nil { - return "", fmt.Errorf("failed to write kubeconfig: %w", err) - } - - return kubeconfigPath, nil -} - -// TODO amarkov: -// I am confused about difference between these modules and modules listed in cluster_creation_test: https://github.com/deckhouse/sds-e2e/pull/20/files#diff-70680e420a441925212ade494a89118228ae9e7058f988299d9c4947238da4e0R122, there are same. Why do we have same modules listed twice? The second modules listed in test is the just example how to enable modules? So we have required modules listed here and ability to install extra modules? -// I think, it's important to allow user to configure ModulePullOverride to install module from MR/PR for development. I see two options: -// Have a single entry point BuildCluster which performs full setup: VMs, deckhouse, modules. Modules are provided via config. Default list of modules is appended to the customized one. Module settings (perhaps, only version) from config override default ones. -// Pros: -// Simpler API -// Easy to have default config and overrides -// Cons: -// Complex config -// Less modular API -// Don't configure modules at all in BuildCluster and configure all modules manually with EnsureModuleEnabled. But it's not clear for me now how to pass default modules. -// Pros: -// More modular API -// Cons: -// More complex API -// Not clear how to pass default configuration -// Pick Option 2 and build simple API on top - -// Default modules must be enabled with EnsureModuleEnabled but not with BuildCluster. We need ability to configure default modules as well - module pull override, settings etc. -func (b *Builder) enableModules(ctx context.Context, nestedCluster cluster.Cluster, dkpCfg *config.DKPClusterConfig) error { - // Enable modules in dependency order - modules := []*config.ModuleConfig{ - {Name: "snapshot-controller", Version: 1, Enabled: true, Dependencies: []string{}}, - {Name: "sds-local-volume", Version: 1, Enabled: true, Dependencies: []string{"snapshot-controller"}}, - {Name: "sds-node-configurator", Version: 1, Enabled: true, Settings: map[string]any{"enableThinProvisioning": true}, Dependencies: []string{"sds-local-volume"}}, - {Name: "sds-replicated-volume", Version: 1, Enabled: true, Dependencies: []string{"sds-node-configurator"}}, - } - - enabled := make(map[string]bool) - - for len(modules) > 0 { - progress := false - for i, module := range modules { - // Check dependencies - allDepsEnabled := true - for _, dep := range module.Dependencies { - if !enabled[dep] { - allDepsEnabled = false - break - } - } - - if allDepsEnabled { - err := nestedCluster.Deckhouse().EnsureModuleEnabled(ctx, module) - if err != nil { - return fmt.Errorf("failed to enable module %s: %w", module.Name, err) - } - - logger.Infof("Enabled module: %s", module.Name) - enabled[module.Name] = true - modules = slices.Delete(modules, i, i+1) - progress = true - break - } - } - - if !progress { - return fmt.Errorf("circular or missing dependencies in modules") - } - } - - return nil -} - -type vmInfo struct { - name string - role string - ip string - vm interface{} // Would be *virt.VirtualMachine -} diff --git a/e2e-tests/internal/cluster/cluster.go b/e2e-tests/internal/cluster/cluster.go deleted file mode 100644 index 2ea3116..0000000 --- a/e2e-tests/internal/cluster/cluster.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cluster - -import ( - "context" - - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes" - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes/deckhouse" - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes/virtualization" - "github.com/deckhouse/sds-e2e-tests/pkg/cluster" -) - -// Cluster implements the cluster.Cluster interface -// TODO amarkov: I am confused with Cluster interface, this implementation, Deckhouse, Virtualization interfaces and implementations. Why do we need them? What value they bring? Just helpers for some operations? Some method performs complex actions while some other methods are just wrappers. -// Is it expected that similar interfaces should be created for any other resource? E.g. I want to work with PVC, DataImport and Job in my test. Should I create interface, implementation and getter in Cluster? Personally, I don't like so much boilerplate to perform simple actions like Get or Create which is actually just wrapper for client. -// If it's not expected to create helper for each resource, it will be not consistent to have manipulate some resource via wrapper and some directly via client. -type Cluster struct { - name string - ctx context.Context - kubeClient *kubernetes.Client - vmClient virtualization.Client - dhClient deckhouse.Client -} - -// NewCluster creates a new cluster instance -func NewCluster(ctx context.Context, name string, kubeClient *kubernetes.Client) *Cluster { - return &Cluster{ - name: name, - ctx: ctx, - kubeClient: kubeClient, - vmClient: virtualization.NewClient(kubeClient), - dhClient: deckhouse.NewClient(kubeClient), - } -} - -// Name returns the cluster name -func (c *Cluster) Name() string { - return c.name -} - -// Context returns the context -func (c *Cluster) Context() context.Context { - return c.ctx -} - -// Virtualization returns the virtualization client -func (c *Cluster) Virtualization() virtualization.Client { - return c.vmClient -} - -// Deckhouse returns the deckhouse client -func (c *Cluster) Deckhouse() deckhouse.Client { - return c.dhClient -} - -// EnsureReady ensures the cluster is ready -func (c *Cluster) EnsureReady(ctx context.Context) error { - // Basic readiness check - can be expanded - return nil -} - -// Close closes the cluster connection -func (c *Cluster) Close() error { - // Cleanup if needed - return nil -} - -// Verify that Cluster implements the interface -var _ cluster.Cluster = (*Cluster)(nil) diff --git a/e2e-tests/internal/cluster/manager.go b/e2e-tests/internal/cluster/manager.go deleted file mode 100644 index 3062c55..0000000 --- a/e2e-tests/internal/cluster/manager.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cluster - -import ( - "context" - "fmt" - "sync" - - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes" - "github.com/deckhouse/sds-e2e-tests/pkg/cluster" -) - -// Manager manages cluster lifecycle -type Manager struct { - clusters map[string]*Cluster // TODO amarkov: Why do we have state here? It is never used. Manager is created only to perform single call. - В коде так: мы создаем новый экземпляр менеджера, выполняем какое-то действие и больше этот менеджер не используется. Зачем тут кеш коннекшнов (clusters map[]*Cluster)? Пример использования https://github.com/deckhouse/sds-e2e/pull/20/files#diff-d091ceebec46a0092794fcbb5caa6afc22969e97953f3e5dd11228a27959d303R36 - mu sync.RWMutex -} - -// NewManager creates a new cluster manager -func NewManager() *Manager { - return &Manager{ - clusters: make(map[string]*Cluster), - } -} - -// GetOrCreate retrieves an existing cluster or creates a new one -func (m *Manager) GetOrCreate(ctx context.Context, configPath, name string) (cluster.Cluster, error) { // TODO amarkov: The name is confusing for me. It seems that it creates k8s cluster somehow, but It creates a connection (client) and wraps into the helper structure Cluster. - Метод ничего не создает. Он получает коннекшн, оборачивает его в cluster.Cluster. Это работа с коннекшнами, а не работа с кластером. ПЕРЕДЕЛАТЬ И ПЕРЕИМЕНОВАТЬ! - m.mu.Lock() - defer m.mu.Unlock() - - key := fmt.Sprintf("%s:%s", configPath, name) - if c, ok := m.clusters[key]; ok { - return c, nil - } - - // Create new cluster - kubeClient, err := kubernetes.NewClient(ctx, configPath, name) - if err != nil { - return nil, fmt.Errorf("failed to create kubernetes client: %w", err) - } - - c := NewCluster(ctx, name, kubeClient) - m.clusters[key] = c - return c, nil -} - -// Get retrieves an existing cluster by key -func (m *Manager) Get(configPath, name string) (cluster.Cluster, bool) { - m.mu.RLock() - defer m.mu.RUnlock() - - key := fmt.Sprintf("%s:%s", configPath, name) - c, ok := m.clusters[key] - return c, ok -} - -// CloseAll closes all managed clusters -func (m *Manager) CloseAll() error { - m.mu.Lock() - defer m.mu.Unlock() - - var errs []error - for _, c := range m.clusters { - if err := c.Close(); err != nil { - errs = append(errs, err) - } - } - m.clusters = make(map[string]*Cluster) - - if len(errs) > 0 { - return fmt.Errorf("errors closing clusters: %v", errs) - } - return nil -} diff --git a/e2e-tests/internal/config/config.go b/e2e-tests/internal/config/config.go deleted file mode 100644 index 4b3e24f..0000000 --- a/e2e-tests/internal/config/config.go +++ /dev/null @@ -1,198 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package config - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "time" -) - -// Config holds all configuration for the e2e tests -type Config struct { // TODO amarkov: Add description of fields and update comment for the struct - // Test namespace configuration - TestNS string - TestNSCleanUp string - KeepState bool - - // Base cluster (hypervisor) configuration - BaseCluster BaseClusterConfig - - // Nested cluster configuration - NestedCluster NestedClusterConfig - - // Feature flags - SkipOptional bool - Parallel bool - TreeMode bool - - // Logging - Verbose bool - Debug bool - LogFile string - - // Paths - DataPath string - KubePath string - RemoteAppPath string - - // File names - PrivKeyName string - PubKeyName string - ConfigName string - ResourcesName string - ConfigTplName string - ResourcesTplName string -} - -// BaseClusterConfig contains configuration for the base Deckhouse cluster (hypervisor) -type BaseClusterConfig struct { - KubeConfig string - SSHHost string - SSHUser string - SSHKey string - K8sPort string - StorageClass string -} - -// NestedClusterConfig contains configuration for the nested cluster being created -type NestedClusterConfig struct { - SSHHost string - SSHUser string - SSHKey string - K8sPort string - KubeConfig string - StorageClass string -} - -// findTestFile traces back through the call stack to find the test file -// that initiated the call chain. It looks for files ending with "_test.go" -func findTestFile() (string, error) { - // Start from the caller of Load() and go up the stack - for i := 1; i < 10; i++ { - _, file, _, ok := runtime.Caller(i) - if !ok { - break - } - // Check if this is a test file - if strings.HasSuffix(file, "_test.go") { - return file, nil - } - } - // If no test file found, return the immediate caller (Load's caller) - _, file, _, ok := runtime.Caller(1) - if !ok { - return "", fmt.Errorf("unable to determine caller file") - } - return file, nil -} - -// Load creates a new Config from environment variables and defaults -func Load() *Config { - // Find the test file path to resolve relative paths - testFile, err := findTestFile() - var baseDir string - if err == nil && testFile != "" { - baseDir = filepath.Dir(testFile) - } else { - // Fallback: use current working directory if we can't find test file - if wd, err := os.Getwd(); err == nil { - baseDir = wd - } else { - baseDir = "." - } - } - - // Resolve absolute paths relative to the test file location - dataPath := filepath.Join(baseDir, "..", "data") - kubePath := filepath.Join(baseDir, "..", "..", "..", "sds-e2e-cfg") - - // Convert to absolute paths - if absDataPath, err := filepath.Abs(dataPath); err == nil { - dataPath = absDataPath - } - if absKubePath, err := filepath.Abs(kubePath); err == nil { - kubePath = absKubePath - } - - cfg := &Config{ - TestNS: generateTestNS(), // TODO amarkov: I'm suggesting to allow specify namespace via environment. It's not convenient to have different namespace each time if you are run same test suite again and again during development. - TestNSCleanUp: "", - KeepState: false, - BaseCluster: BaseClusterConfig{ - KubeConfig: os.Getenv("BASE_KUBECONFIG"), - SSHHost: os.Getenv("BASE_SSH_HOST"), - SSHUser: os.Getenv("BASE_SSH_USER"), - SSHKey: os.Getenv("BASE_SSH_KEY"), - K8sPort: getEnvOrDefault("BASE_K8S_PORT", "6445"), - StorageClass: getEnvOrDefault("BASE_STORAGE_CLASS", "linstor-r1"), - }, - NestedCluster: NestedClusterConfig{ - SSHHost: getEnvOrDefault("NESTED_SSH_HOST", "127.0.0.1"), - SSHUser: getEnvOrDefault("NESTED_SSH_USER", "user"), - SSHKey: os.Getenv("NESTED_SSH_KEY"), - K8sPort: getEnvOrDefault("NESTED_K8S_PORT", "6445"), - KubeConfig: getEnvOrDefault("NESTED_KUBECONFIG", "kube-nested.config"), - StorageClass: getEnvOrDefault("NESTED_STORAGE_CLASS", "linstor-r1"), - }, - SkipOptional: false, - Parallel: false, - TreeMode: false, - Verbose: false, - Debug: false, - LogFile: os.Getenv("LOG_FILE"), - DataPath: dataPath, - KubePath: kubePath, - RemoteAppPath: "/home/user", - PrivKeyName: "id_rsa_test", - PubKeyName: "id_rsa_test.pub", - ConfigName: "config.yml", - ResourcesName: "resources.yml", - ConfigTplName: "config.yml.tpl", - ResourcesTplName: "resources.yml.tpl", - } - - // Ensure paths are absolute - if !filepath.IsAbs(cfg.KubePath) { - if absPath, err := filepath.Abs(cfg.KubePath); err == nil { - cfg.KubePath = absPath - } - } - if !filepath.IsAbs(cfg.NestedCluster.KubeConfig) { - cfg.NestedCluster.KubeConfig = filepath.Join(cfg.KubePath, cfg.NestedCluster.KubeConfig) - } - if cfg.BaseCluster.KubeConfig != "" && !filepath.IsAbs(cfg.BaseCluster.KubeConfig) { - cfg.BaseCluster.KubeConfig = filepath.Join(cfg.KubePath, cfg.BaseCluster.KubeConfig) - } - - return cfg -} - -func generateTestNS() string { - now := time.Now() - return fmt.Sprintf("e2e-tmp-%d%d", now.Minute(), now.Second()) -} - -func getEnvOrDefault(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value - } - return defaultValue -} diff --git a/e2e-tests/internal/config/types.go b/e2e-tests/internal/config/types.go index 42849b6..7a19665 100644 --- a/e2e-tests/internal/config/types.go +++ b/e2e-tests/internal/config/types.go @@ -16,7 +16,12 @@ limitations under the License. package config -import "time" +import ( + "fmt" + "time" + + "gopkg.in/yaml.v3" +) // HostType represents the type of host (VM or bare-metal) type HostType string @@ -65,53 +70,57 @@ const ( // NodeAuth contains authentication information for a node type NodeAuth struct { - Method AuthMethod - User string - SSHKey string // Public key (value like "ssh-rsa ...", path to .pub file, or empty for default) - Password string // Password (if using password auth) + Method AuthMethod `yaml:"method"` + User string `yaml:"user"` + SSHKey string `yaml:"sshKey"` // Public key (value like "ssh-rsa ...", path to .pub file, or empty for default) + Password string `yaml:"password,omitempty"` // Password (if using password auth) } // ClusterNode defines a single node in the cluster type ClusterNode struct { - Hostname string - IPAddress string // Required for bare-metal, optional for VM - OSType OSType // Required for VM, optional for bare-metal - HostType HostType - Role ClusterRole - Auth NodeAuth + Hostname string `yaml:"hostname"` + IPAddress string `yaml:"ipAddress,omitempty"` // Required for bare-metal, optional for VM + OSType OSType `yaml:"osType"` // Required for VM, optional for bare-metal (custom unmarshaler handles string -> OSType conversion) + HostType HostType `yaml:"hostType"` + Role ClusterRole `yaml:"role"` + Auth NodeAuth `yaml:"auth"` // VM-specific fields (only used when HostType == HostTypeVM) - CPU int // Required for VM - RAM int // Required for VM, in GB - DiskSize int // Required for VM, in GB + CPU int `yaml:"cpu"` // Required for VM + RAM int `yaml:"ram"` // Required for VM, in GB + DiskSize int `yaml:"diskSize"` // Required for VM, in GB // Bare-metal specific fields - Prepared bool // Whether the node is already prepared for DKP installation + Prepared bool `yaml:"prepared,omitempty"` // Whether the node is already prepared for DKP installation +} + +// DKPParameters defines DKP-specific parameters for cluster deployment +type DKPParameters struct { + KubernetesVersion string `yaml:"kubernetesVersion"` + PodSubnetCIDR string `yaml:"podSubnetCIDR"` + ServiceSubnetCIDR string `yaml:"serviceSubnetCIDR"` + ClusterDomain string `yaml:"clusterDomain"` + LicenseKey string `yaml:"licenseKey"` + RegistryRepo string `yaml:"registryRepo"` + Namespace string `yaml:"namespace"` + StorageClass string `yaml:"storageClass"` + Modules []*ModuleConfig `yaml:"modules,omitempty"` } // ClusterDefinition defines the complete cluster configuration type ClusterDefinition struct { - Masters []ClusterNode - Workers []ClusterNode - Setup *ClusterNode // Bootstrap node (can be nil) + Masters []ClusterNode `yaml:"masters"` + Workers []ClusterNode `yaml:"workers"` + Setup *ClusterNode `yaml:"setup,omitempty"` // Bootstrap node (can be nil) + DKPParameters DKPParameters `yaml:"dkpParameters"` } // ModuleConfig defines a Deckhouse module configuration type ModuleConfig struct { // TODO amarkov: I suggest allow user to specify ModulePullOverride version, to run tests on MR/PR during development process. - Name string - Version int - Enabled bool - Settings map[string]any - Dependencies []string // Names of modules that must be enabled before this one -} - -// DKPClusterConfig defines the Deckhouse Kubernetes Platform cluster configuration -type DKPClusterConfig struct { - ClusterDefinition ClusterDefinition - KubernetesVersion string - PodSubnetCIDR string - ServiceSubnetCIDR string - ClusterDomain string - LicenseKey string - RegistryRepo string + Name string `yaml:"name"` + Version int `yaml:"version"` + Enabled bool `yaml:"enabled"` + Settings map[string]any `yaml:"settings,omitempty"` + Dependencies []string `yaml:"dependencies,omitempty"` // Names of modules that must be enabled before this one + ModulePullOverride string `yaml:"modulePullOverride,omitempty"` // Override the module pull branch or tag (e.g. "main", "pr123", "mr41") } const ( @@ -119,3 +128,117 @@ const ( DKPDeployTimeout = 30 * time.Minute // Timeout for DKP deployment ModuleDeployTimeout = 10 * time.Minute // Timeout for module deployment ) + +// UnmarshalYAML implements custom YAML unmarshaling for ClusterNode +// to handle OSType conversion from string key to OSType struct +func (n *ClusterNode) UnmarshalYAML(value *yaml.Node) error { + // Temporary struct with OSType as string for unmarshaling + type clusterNodeTmp struct { + Hostname string `yaml:"hostname"` + IPAddress string `yaml:"ipAddress,omitempty"` + OSType string `yaml:"osType"` + HostType string `yaml:"hostType"` + Role string `yaml:"role"` + Auth NodeAuth `yaml:"auth"` + CPU int `yaml:"cpu"` + RAM int `yaml:"ram"` + DiskSize int `yaml:"diskSize"` + Prepared bool `yaml:"prepared,omitempty"` + } + + var tmp clusterNodeTmp + if err := value.Decode(&tmp); err != nil { + return err + } + + // Convert HostType + hostType := HostType(tmp.HostType) + if hostType != HostTypeVM && hostType != HostTypeBareMetal { + return fmt.Errorf("invalid hostType: %s", tmp.HostType) + } + + // Convert Role + role := ClusterRole(tmp.Role) + if role != ClusterRoleMaster && role != ClusterRoleWorker && role != ClusterRoleSetup { + return fmt.Errorf("invalid role: %s", tmp.Role) + } + + // Convert OSType string key to OSType struct + osType, ok := OSTypeMap[tmp.OSType] + if !ok { + return fmt.Errorf("unknown osType: %s", tmp.OSType) + } + + // Convert AuthMethod + authMethod := AuthMethod(tmp.Auth.Method) + if authMethod != AuthMethodSSHKey && authMethod != AuthMethodSSHPass { + return fmt.Errorf("invalid auth method: %s", tmp.Auth.Method) + } + + // Assign to actual struct + n.Hostname = tmp.Hostname + n.IPAddress = tmp.IPAddress + n.OSType = osType + n.HostType = hostType + n.Role = role + n.Auth = NodeAuth{ + Method: authMethod, + User: tmp.Auth.User, + SSHKey: tmp.Auth.SSHKey, + Password: tmp.Auth.Password, + } + n.CPU = tmp.CPU + n.RAM = tmp.RAM + n.DiskSize = tmp.DiskSize + n.Prepared = tmp.Prepared + + return nil +} + +// UnmarshalYAML implements custom YAML unmarshaling for ClusterDefinition +// to handle the top-level "clusterDefinition:" key in the YAML +func (c *ClusterDefinition) UnmarshalYAML(value *yaml.Node) error { + // Check if we have a top-level "clusterDefinition" key + if value.Kind == yaml.MappingNode && len(value.Content) > 0 { + // Look for "clusterDefinition" key + for i := 0; i < len(value.Content)-1; i += 2 { + if value.Content[i].Value == "clusterDefinition" { + // Found the key, decode the value (next node) into a temporary struct + // to avoid infinite recursion + type clusterDefTmp struct { + Masters []ClusterNode `yaml:"masters"` + Workers []ClusterNode `yaml:"workers"` + Setup *ClusterNode `yaml:"setup,omitempty"` + DKPParameters DKPParameters `yaml:"dkpParameters"` + } + var tmp clusterDefTmp + if err := value.Content[i+1].Decode(&tmp); err != nil { + return err + } + // Copy to actual struct + c.Masters = tmp.Masters + c.Workers = tmp.Workers + c.Setup = tmp.Setup + c.DKPParameters = tmp.DKPParameters + return nil + } + } + } + // If no "clusterDefinition" key found, decode directly using temporary struct + // to avoid infinite recursion + type clusterDefTmp struct { + Masters []ClusterNode `yaml:"masters"` + Workers []ClusterNode `yaml:"workers"` + Setup *ClusterNode `yaml:"setup,omitempty"` + DKPParameters DKPParameters `yaml:"dkpParameters"` + } + var tmp clusterDefTmp + if err := value.Decode(&tmp); err != nil { + return err + } + c.Masters = tmp.Masters + c.Workers = tmp.Workers + c.Setup = tmp.Setup + c.DKPParameters = tmp.DKPParameters + return nil +} diff --git a/e2e-tests/internal/infrastructure/ssh/client.go b/e2e-tests/internal/infrastructure/ssh/client.go new file mode 100644 index 0000000..6d3dfe9 --- /dev/null +++ b/e2e-tests/internal/infrastructure/ssh/client.go @@ -0,0 +1,268 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ssh + +import ( + "context" + "fmt" + "io" + "net" + "os" + "os/user" + "path/filepath" + "strings" + "syscall" + + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" + "golang.org/x/term" +) + +// client implements Client interface +type client struct { + sshClient *ssh.Client +} + +// NewFactory creates a new SSH factory +func NewFactory() SSHFactory { + return &factory{} +} + +// readPassword reads a password from the terminal +func readPassword(prompt string) ([]byte, error) { + fmt.Fprint(os.Stderr, prompt) + var fd int + if term.IsTerminal(syscall.Stdin) { + fd = syscall.Stdin + } else { + tty, err := os.Open("/dev/tty") + if err != nil { + return nil, fmt.Errorf("error allocating terminal: %w", err) + } + defer tty.Close() + fd = int(tty.Fd()) + } + + pass, err := term.ReadPassword(fd) + fmt.Fprintln(os.Stderr) + return pass, err +} + +// expandPath expands ~ to home directory +func expandPath(path string) (string, error) { + if !strings.HasPrefix(path, "~") { + return path, nil + } + + usr, err := user.Current() + if err != nil { + return "", fmt.Errorf("failed to get current user: %w", err) + } + + if path == "~" { + return usr.HomeDir, nil + } + + return filepath.Join(usr.HomeDir, strings.TrimPrefix(path, "~/")), nil +} + +// createSSHConfig creates SSH client config with support for passphrase-protected keys +func createSSHConfig(user, keyPath string) (*ssh.ClientConfig, error) { + expandedKeyPath, err := expandPath(keyPath) + if err != nil { + return nil, fmt.Errorf("failed to expand key path: %w", err) + } + + key, err := os.ReadFile(expandedKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read private key %s: %w", expandedKeyPath, err) + } + + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + if !strings.Contains(err.Error(), "ssh: this private key is passphrase protected") { + return nil, fmt.Errorf("unable to parse private key: %w", err) + } + + // Try to get passphrase from environment variable first + var pass []byte + if envPass := os.Getenv("SSH_PASSPHRASE"); envPass != "" { + pass = []byte(envPass) + } else { + // Try to read from terminal + var readErr error + pass, readErr = readPassword(" Enter passphrase for '" + expandedKeyPath + "': ") + if readErr != nil { + return nil, fmt.Errorf("SSH key '%s' is passphrase protected. Set SSH_PASSPHRASE environment variable: export SSH_PASSPHRASE='your-passphrase'\nOriginal error: %w", expandedKeyPath, readErr) + } + } + + signer, err = ssh.ParsePrivateKeyWithPassphrase(key, pass) + if err != nil { + return nil, fmt.Errorf("unable to parse private key with passphrase: %w", err) + } + } + + return &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, nil +} + +// Create creates a new SSH client +func (c *client) Create(user, host, keyPath string) (SSHClient, error) { + sshConfig, err := createSSHConfig(user, keyPath) + if err != nil { + return nil, fmt.Errorf("failed to create SSH config: %w", err) + } + + // Ensure host has port if not specified + addr := host + if !strings.Contains(addr, ":") { + addr = addr + ":22" + } + + sshClient, err := ssh.Dial("tcp", addr, sshConfig) + if err != nil { + return nil, fmt.Errorf("failed to connect to %s@%s: %w", user, addr, err) + } + + return &client{sshClient: sshClient}, nil +} + +// CreateForward creates an SSH client with port forwarding +func (c *client) CreateForward(user, host, keyPath string, localPort, remotePort string) (SSHClient, error) { + // First create a regular connection + baseClient, err := c.Create(user, host, keyPath) + if err != nil { + return nil, err + } + + // Set up port forwarding + baseClientImpl := baseClient.(*client) + listener, err := net.Listen("tcp", "localhost:"+localPort) + if err != nil { + baseClientImpl.Close() + return nil, fmt.Errorf("failed to listen on local port %s: %w", localPort, err) + } + + go func() { + for { + localConn, err := listener.Accept() + if err != nil { + return + } + + remoteConn, err := baseClientImpl.sshClient.Dial("tcp", "localhost:"+remotePort) + if err != nil { + localConn.Close() + continue + } + + go func() { + io.Copy(localConn, remoteConn) + localConn.Close() + remoteConn.Close() + }() + go func() { + io.Copy(remoteConn, localConn) + localConn.Close() + remoteConn.Close() + }() + } + }() + + return baseClient, nil +} + +// Exec executes a command on the remote host +func (c *client) Exec(ctx context.Context, cmd string) (string, error) { + session, err := c.sshClient.NewSession() + if err != nil { + return "", fmt.Errorf("failed to create SSH session: %w", err) + } + defer session.Close() + + output, err := session.CombinedOutput(cmd) + if err != nil { + return string(output), fmt.Errorf("command failed: %w", err) + } + + return string(output), nil +} + +// ExecFatal executes a command and returns error if it fails +func (c *client) ExecFatal(ctx context.Context, cmd string) string { + output, err := c.Exec(ctx, cmd) + if err != nil { + panic(fmt.Sprintf("ExecFatal failed for command '%s': %v\nOutput: %s", cmd, err, output)) + } + return output +} + +// Upload uploads a local file to the remote host +func (c *client) Upload(ctx context.Context, localPath, remotePath string) error { + sftpClient, err := sftp.NewClient(c.sshClient) + if err != nil { + return fmt.Errorf("failed to create SFTP client: %w", err) + } + defer sftpClient.Close() + + localFile, err := os.Open(localPath) + if err != nil { + return fmt.Errorf("failed to open local file %s: %w", localPath, err) + } + defer localFile.Close() + + remoteFile, err := sftpClient.Create(remotePath) + if err != nil { + return fmt.Errorf("failed to create remote file %s: %w", remotePath, err) + } + defer remoteFile.Close() + + _, err = io.Copy(remoteFile, localFile) + if err != nil { + return fmt.Errorf("failed to copy file: %w", err) + } + + return nil +} + +// Close closes the SSH connection +func (c *client) Close() error { + if c.sshClient != nil { + return c.sshClient.Close() + } + return nil +} + +// NewClient creates a new SSH client +func NewClient(user, host, keyPath string) (SSHClient, error) { + var c client + return c.Create(user, host, keyPath) +} + +// factory implements Factory interface +type factory struct{} + +// CreateClient creates a new SSH client +func (f *factory) CreateClient(user, host, keyPath string) (SSHClient, error) { + return NewClient(user, host, keyPath) +} diff --git a/e2e-tests/internal/infrastructure/ssh/interface.go b/e2e-tests/internal/infrastructure/ssh/interface.go index 237072b..6a35de8 100644 --- a/e2e-tests/internal/infrastructure/ssh/interface.go +++ b/e2e-tests/internal/infrastructure/ssh/interface.go @@ -18,27 +18,28 @@ package ssh import "context" -// Client provides SSH operations -type Client interface { +// SSHClient provides SSH operations +type SSHClient interface { + // Create creates a new SSH client + Create(user, host, keyPath string) (SSHClient, error) + + // CreateForward creates an SSH client with port forwarding + CreateForward(user, host, keyPath string, localPort, remotePort string) (SSHClient, error) + // Exec executes a command on the remote host Exec(ctx context.Context, cmd string) (string, error) - + // ExecFatal executes a command and returns error if it fails ExecFatal(ctx context.Context, cmd string) string - - // Upload uploads a file to the remote host + + // Uploads a local file to the remote host Upload(ctx context.Context, localPath, remotePath string) error - + // Close closes the SSH connection Close() error } -// Factory creates SSH clients -type Factory interface { - // CreateClient creates a new SSH client - CreateClient(user, host, keyPath string) (Client, error) - - // CreateForwardClient creates an SSH client with port forwarding - CreateForwardClient(user, host, keyPath string, localPort, remotePort string) (Client, error) +// Factory provides a way to create SSH clients +type SSHFactory interface { + CreateClient(user, host, keyPath string) (SSHClient, error) } - diff --git a/e2e-tests/internal/kubernetes/client.go b/e2e-tests/internal/kubernetes/client.go deleted file mode 100644 index d75ea5d..0000000 --- a/e2e-tests/internal/kubernetes/client.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package kubernetes - -import ( - "context" - "fmt" - - logr "github.com/go-logr/logr" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - kubescheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - ctrlrtclient "sigs.k8s.io/controller-runtime/pkg/client" - ctrlrtlog "sigs.k8s.io/controller-runtime/pkg/log" - - coreapi "k8s.io/api/core/v1" - storapi "k8s.io/api/storage/v1" - extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiruntime "k8s.io/apimachinery/pkg/runtime" - - snc "github.com/deckhouse/sds-node-configurator/api/v1alpha1" - srv "github.com/deckhouse/sds-replicated-volume/api/v1alpha1" - virt "github.com/deckhouse/virtualization/api/core/v1alpha2" -) - -// Client wraps Kubernetes API clients -type Client struct { - ctx context.Context // TODO amarkov: I'm not sure how good it is in Go to store context. Should we rather pass it in each call as usual? - Context should better be passed as an argument, do not keep in in struct. - restCfg *rest.Config - controllerRuntimeClient ctrlrtclient.Client - goClient *kubernetes.Clientset - dyClient *dynamic.DynamicClient -} - -// NewClient creates a new Kubernetes client -func NewClient(ctx context.Context, configPath, clusterName string) (*Client, error) { - restCfg, err := newRestConfig(configPath, clusterName) - if err != nil { - return nil, fmt.Errorf("failed to create rest config: %w", err) - } - - rcl, err := newKubeRTClient(restCfg) - if err != nil { - return nil, fmt.Errorf("failed to create controller runtime client: %w", err) - } - - gcl, err := kubernetes.NewForConfig(restCfg) - if err != nil { - return nil, fmt.Errorf("failed to create kubernetes clientset: %w", err) - } - - dcl, err := dynamic.NewForConfig(restCfg) - if err != nil { - return nil, fmt.Errorf("failed to create dynamic client: %w", err) - } - - return &Client{ - ctx: ctx, - restCfg: restCfg, - controllerRuntimeClient: rcl, - goClient: gcl, - dyClient: dcl, - }, nil -} - -func newRestConfig(configPath, clusterName string) (*rest.Config, error) { - var loader clientcmd.ClientConfigLoader - if configPath != "" { - loader = &clientcmd.ClientConfigLoadingRules{ExplicitPath: configPath} - } else { - loader = clientcmd.NewDefaultClientConfigLoadingRules() - } - - overrides := clientcmd.ConfigOverrides{} - if clusterName != "" { - overrides.Context.Cluster = clusterName - overrides.CurrentContext = clusterName - } - - cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - loader, &overrides).ClientConfig() - if err != nil { - return nil, fmt.Errorf("failed to create rest config: %w", err) - } - - return cfg, nil -} - -func newKubeRTClient(cfg *rest.Config) (ctrlrtclient.Client, error) { - scheme := apiruntime.NewScheme() - - // Add schemes - schemeFuncs := []func(*apiruntime.Scheme) error{ - virt.AddToScheme, - srv.AddToScheme, - snc.AddToScheme, - kubescheme.AddToScheme, - extapi.AddToScheme, - coreapi.AddToScheme, - storapi.AddToScheme, - // Add Deckhouse scheme - we'll need to define this separately - } - - for _, f := range schemeFuncs { - if err := f(scheme); err != nil { - return nil, fmt.Errorf("failed to add scheme: %w", err) - } - } - - // TODO amarkov: Ginkgo provides own logger with logr interface: https://onsi.github.io/ginkgo/#logging-output - // Instead of creating logr from new context (context.Background) I recommend propagate logger from the topmost call, so user can pass GingkoWriter as logging backend. It can be propagated either as arguments or as context. - // mrwesadex-flant - // У Гинкго проблема с логгированием - оно проглатыывает все логи до ошибки. Если нет ошибки, нет и логов. У нее есть свой логгер - GinkgoWriter - лучше использовать его. Есть реализация интерфейса logr - можно посмотреть в сторону нее. - - ctrlrtlog.SetLogger(logr.FromContextOrDiscard(context.Background())) - cl, err := ctrlrtclient.New(cfg, ctrlrtclient.Options{ - Scheme: scheme, - }) - if err != nil { - return nil, err - } - - return cl, nil -} - -// ControllerRuntimeClient returns the controller-runtime client -func (c *Client) ControllerRuntimeClient() ctrlrtclient.Client { - return c.controllerRuntimeClient -} - -// GoClient returns the kubernetes clientset -func (c *Client) GoClient() *kubernetes.Clientset { - return c.goClient -} - -// DynamicClient returns the dynamic client -func (c *Client) DynamicClient() *dynamic.DynamicClient { - return c.dyClient -} - -// Context returns the context -func (c *Client) Context() context.Context { - return c.ctx -} diff --git a/e2e-tests/internal/kubernetes/deckhouse/client.go b/e2e-tests/internal/kubernetes/deckhouse/client.go deleted file mode 100644 index 24c1c58..0000000 --- a/e2e-tests/internal/kubernetes/deckhouse/client.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package deckhouse - -import ( - "context" - "fmt" - - v1alpha1nfs "github.com/deckhouse/csi-nfs/api/v1alpha1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" - ctrlrtclient "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/deckhouse/sds-e2e-tests/internal/config" - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes" - "github.com/deckhouse/sds-e2e-tests/internal/utils" -) - -// Client provides operations on Deckhouse modules -type Client interface { - // EnsureModuleEnabled ensures a module is enabled with given configuration - EnsureModuleEnabled(ctx context.Context, moduleCfg *config.ModuleConfig) error - - // WaitForModuleReady waits for a module to be ready - WaitForModuleReady(ctx context.Context, moduleName string, timeoutSeconds int) error -} - -type client struct { - kubeClient *kubernetes.Client -} - -// NewClient creates a new deckhouse client -func NewClient(kubeClient *kubernetes.Client) Client { - return &client{kubeClient: kubeClient} -} - -func (c *client) EnsureModuleEnabled(ctx context.Context, moduleCfg *config.ModuleConfig) error { - moduleConfig := &v1alpha1nfs.ModuleConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: moduleCfg.Name, - }, - Spec: v1alpha1nfs.ModuleConfigSpec{ - Version: moduleCfg.Version, - Enabled: ptr.To(moduleCfg.Enabled), - Settings: moduleCfg.Settings, - }, - } - - err := c.kubeClient.ControllerRuntimeClient().Create(ctx, moduleConfig) - if err != nil { - if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("failed to create module config: %w", err) - } - - // Update existing - existing := &v1alpha1nfs.ModuleConfig{} - err = c.kubeClient.ControllerRuntimeClient().Get(ctx, ctrlrtclient.ObjectKey{Name: moduleCfg.Name}, existing) - if err != nil { - return fmt.Errorf("failed to get existing module config: %w", err) - } - - moduleConfig.ResourceVersion = existing.ResourceVersion - err = c.kubeClient.ControllerRuntimeClient().Update(ctx, moduleConfig) - if err != nil { - return fmt.Errorf("failed to update module config: %w", err) - } - } - - return nil -} - -// TODO amarkov: Need to rework WaitForXXXReady to check (IsXXXReady) and Wait wrapper. -func (c *client) WaitForModuleReady(ctx context.Context, moduleName string, timeoutSeconds int) error { - // This is a simplified implementation - in production, you'd check for actual deployments/daemonsets - return utils.RetrySec(timeoutSeconds, func() error { - // For now, just check that module config exists and is enabled - moduleConfig := &v1alpha1nfs.ModuleConfig{} - err := c.kubeClient.ControllerRuntimeClient().Get(ctx, ctrlrtclient.ObjectKey{Name: moduleName}, moduleConfig) - if err != nil { - return fmt.Errorf("module %s not found: %w", moduleName, err) - } - - if moduleConfig.Spec.Enabled == nil || !*moduleConfig.Spec.Enabled { - return fmt.Errorf("module %s is not enabled", moduleName) - } - - return nil - }) -} diff --git a/e2e-tests/internal/kubernetes/virtualization/client.go b/e2e-tests/internal/kubernetes/virtualization/client.go deleted file mode 100644 index 5cadfed..0000000 --- a/e2e-tests/internal/kubernetes/virtualization/client.go +++ /dev/null @@ -1,323 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package virtualization - -import ( - "context" - "fmt" - - virt "github.com/deckhouse/virtualization/api/core/v1alpha2" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrlrtclient "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes" - "github.com/deckhouse/sds-e2e-tests/internal/utils" -) - -// Client provides operations on virtualization resources (VMs, disks, etc.) -type Client interface { - // CreateVM creates a virtual machine - CreateVM(ctx context.Context, req VMRequest) (*virt.VirtualMachine, error) - - // GetVM retrieves a virtual machine by name - GetVM(ctx context.Context, namespace, name string) (*virt.VirtualMachine, error) - - // ListVMs lists virtual machines with optional filters - ListVMs(ctx context.Context, namespace string) ([]virt.VirtualMachine, error) - - // WaitForVMRunning waits for a VM to be in Running phase - WaitForVMRunning(ctx context.Context, namespace, name string, timeoutSeconds int) error - - // GetVMIP gets the IP address of a running VM - GetVMIP(ctx context.Context, namespace, name string) (string, error) -} - -// VMRequest contains parameters for creating a VM -type VMRequest struct { - Name string - Namespace string - Image string // Image name or URL - ImageURL string // Full image URL (derived from Image if Image is a key in OSTypeMap) - CPU int - RAM int // In GB - DiskSize int // In GB - StorageClass string - SSHPubKey string - IPAddress string // Optional static IP -} - -type client struct { - kubeClient *kubernetes.Client -} - -// NewClient creates a new virtualization client -func NewClient(kubeClient *kubernetes.Client) Client { - return &client{kubeClient: kubeClient} -} - -func (c *client) CreateVM(ctx context.Context, req VMRequest) (*virt.VirtualMachine, error) { - // Get or create cluster virtual image - cvmi, err := c.getOrCreateClusterVirtualImage(ctx, req.Image, req.ImageURL) - if err != nil { - return nil, fmt.Errorf("failed to get/create cluster virtual image: %w", err) - } - - // Create IP claim if IP is provided - ipClaimName := "" - if req.IPAddress != "" { - ipClaim, err := c.createOrGetIPClaim(ctx, req.Namespace, req.Name, req.IPAddress) - if err != nil { - return nil, fmt.Errorf("failed to create/get IP claim: %w", err) - } - ipClaimName = ipClaim.Name - } - - // Create system disk - systemDisk, err := c.createOrGetSystemDisk(ctx, req.Namespace, req.Name, req.StorageClass, req.DiskSize, cvmi) - if err != nil { - return nil, fmt.Errorf("failed to create/get system disk: %w", err) - } - - // Create VM - memory, err := resource.ParseQuantity(fmt.Sprintf("%dGi", req.RAM)) - if err != nil { - return nil, fmt.Errorf("failed to parse memory: %w", err) - } - - vm := &virt.VirtualMachine{ - ObjectMeta: metav1.ObjectMeta{ - Name: req.Name, - Namespace: req.Namespace, - Labels: map[string]string{"vm": "linux", "service": "v1"}, - }, - Spec: virt.VirtualMachineSpec{ - VirtualMachineClassName: "generic", - EnableParavirtualization: true, - RunPolicy: virt.RunPolicy("AlwaysOn"), - OsType: virt.OsType("Generic"), - Bootloader: virt.BootloaderType("BIOS"), - VirtualMachineIPAddress: ipClaimName, - CPU: virt.CPUSpec{Cores: req.CPU, CoreFraction: "100%"}, - Memory: virt.MemorySpec{Size: memory}, - BlockDeviceRefs: []virt.BlockDeviceSpecRef{ - { - Kind: virt.DiskDevice, - Name: systemDisk.Name, - }, - }, - Provisioning: &virt.Provisioning{ - Type: virt.ProvisioningType("UserData"), - UserData: fmt.Sprintf(`#cloud-config -package_update: true -packages: -- qemu-guest-agent -runcmd: -- [ hostnamectl, set-hostname, %s ] -- [ systemctl, daemon-reload ] -- [ systemctl, enable, --now, qemu-guest-agent.service ] -user: user -password: user -ssh_pwauth: True -chpasswd: { expire: False } -sudo: ALL=(ALL) NOPASSWD:ALL -chpasswd: { expire: False } -ssh_authorized_keys: - - %s -`, req.Name, req.SSHPubKey), - }, - }, - } - - err = c.kubeClient.ControllerRuntimeClient().Create(ctx, vm) - if err != nil && !apierrors.IsAlreadyExists(err) { - return nil, fmt.Errorf("failed to create VM: %w", err) - } - - return vm, nil -} - -func (c *client) GetVM(ctx context.Context, namespace, name string) (*virt.VirtualMachine, error) { - vm := &virt.VirtualMachine{} - err := c.kubeClient.ControllerRuntimeClient().Get(ctx, ctrlrtclient.ObjectKey{ - Namespace: namespace, - Name: name, - }, vm) - if err != nil { - return nil, err - } - return vm, nil -} - -func (c *client) ListVMs(ctx context.Context, namespace string) ([]virt.VirtualMachine, error) { - vmList := &virt.VirtualMachineList{} - opts := []ctrlrtclient.ListOption{} - if namespace != "" { - opts = append(opts, ctrlrtclient.InNamespace(namespace)) - } - - err := c.kubeClient.ControllerRuntimeClient().List(ctx, vmList, opts...) - if err != nil { - return nil, err - } - return vmList.Items, nil -} - -func (c *client) WaitForVMRunning(ctx context.Context, namespace, name string, timeoutSeconds int) error { - return utils.RetrySec(timeoutSeconds, func() error { - vm, err := c.GetVM(ctx, namespace, name) - if err != nil { - return err - } - if vm.Status.Phase != virt.MachineRunning { - return fmt.Errorf("VM %s/%s is not running, phase: %s", namespace, name, vm.Status.Phase) - } - return nil - }) -} - -func (c *client) GetVMIP(ctx context.Context, namespace, name string) (string, error) { - vm, err := c.GetVM(ctx, namespace, name) - if err != nil { - return "", err - } - if vm.Status.IPAddress == "" { - return "", fmt.Errorf("VM %s/%s does not have an IP address yet", namespace, name) - } - return vm.Status.IPAddress, nil -} - -// Helper methods - -func (c *client) getOrCreateClusterVirtualImage(ctx context.Context, imageName, imageURL string) (*virt.ClusterVirtualImage, error) { - // Derive name from image - cvmiName := utils.HashMd5(imageURL)[:8] - - // Try to get existing - cvmi := &virt.ClusterVirtualImage{} - err := c.kubeClient.ControllerRuntimeClient().Get(ctx, ctrlrtclient.ObjectKey{Name: cvmiName}, cvmi) - if err == nil { - return cvmi, nil - } - if !apierrors.IsNotFound(err) { - return nil, err - } - - // Create new - cvmi = &virt.ClusterVirtualImage{ - ObjectMeta: metav1.ObjectMeta{ - Name: cvmiName, - }, - Spec: virt.ClusterVirtualImageSpec{ - DataSource: virt.ClusterVirtualImageDataSource{ - Type: "HTTP", - HTTP: &virt.DataSourceHTTP{URL: imageURL}, - }, - }, - } - - err = c.kubeClient.ControllerRuntimeClient().Create(ctx, cvmi) - if err != nil { - return nil, err - } - - return cvmi, nil -} - -func (c *client) createOrGetIPClaim(ctx context.Context, namespace, vmName, ip string) (*virt.VirtualMachineIPAddress, error) { - claimName := fmt.Sprintf("%s-ipaddress-0", vmName) - - claim := &virt.VirtualMachineIPAddress{} - err := c.kubeClient.ControllerRuntimeClient().Get(ctx, ctrlrtclient.ObjectKey{ - Namespace: namespace, - Name: claimName, - }, claim) - if err == nil { - return claim, nil - } - if !apierrors.IsNotFound(err) { - return nil, err - } - - claim = &virt.VirtualMachineIPAddress{ - ObjectMeta: metav1.ObjectMeta{ - Name: claimName, - Namespace: namespace, - }, - Spec: virt.VirtualMachineIPAddressSpec{ - Type: virt.VirtualMachineIPAddressTypeStatic, - StaticIP: ip, - }, - } - - err = c.kubeClient.ControllerRuntimeClient().Create(ctx, claim) - if err != nil { - return nil, err - } - - return claim, nil -} - -func (c *client) createOrGetSystemDisk(ctx context.Context, namespace, vmName, storageClass string, diskSize int, cvmi *virt.ClusterVirtualImage) (*virt.VirtualDisk, error) { - diskName := fmt.Sprintf("%s-system", vmName) - - disk := &virt.VirtualDisk{} - err := c.kubeClient.ControllerRuntimeClient().Get(ctx, ctrlrtclient.ObjectKey{ - Namespace: namespace, - Name: diskName, - }, disk) - if err == nil { - return disk, nil - } - if !apierrors.IsNotFound(err) { - return nil, err - } - - var sc *string = nil - if storageClass != "" { - sc = &storageClass - } - - disk = &virt.VirtualDisk{ - ObjectMeta: metav1.ObjectMeta{ - Name: diskName, - Namespace: namespace, - }, - Spec: virt.VirtualDiskSpec{ - PersistentVolumeClaim: virt.VirtualDiskPersistentVolumeClaim{ - Size: resource.NewQuantity(int64(diskSize)*1024*1024*1024, resource.BinarySI), - StorageClass: sc, - }, - DataSource: &virt.VirtualDiskDataSource{ - Type: virt.DataSourceTypeObjectRef, - ObjectRef: &virt.VirtualDiskObjectRef{ - Kind: virt.ClusterVirtualImageKind, - Name: cvmi.Name, - }, - }, - }, - } - - err = c.kubeClient.ControllerRuntimeClient().Create(ctx, disk) - if err != nil { - return nil, err - } - - return disk, nil -} - diff --git a/e2e-tests/internal/logger/logger.go b/e2e-tests/internal/logger/logger.go deleted file mode 100644 index 796cb95..0000000 --- a/e2e-tests/internal/logger/logger.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package logger - -import ( - "fmt" - "log" - "os" -) - -// Logger interface for dependency injection -type Logger interface { - Debug(msg string, args ...interface{}) - Info(msg string, args ...interface{}) - Warn(msg string, args ...interface{}) - Error(msg string, args ...interface{}) - Fatal(msg string, args ...interface{}) -} - -// SimpleLogger is a basic implementation of Logger -type SimpleLogger struct { - debug bool - logger *log.Logger -} - -// NewSimpleLogger creates a new simple logger -func NewSimpleLogger(debug bool) *SimpleLogger { - return &SimpleLogger{ - debug: debug, - logger: log.New(os.Stdout, "", log.LstdFlags), - } -} - -func (l *SimpleLogger) Debug(msg string, args ...interface{}) { - if l.debug { - l.logger.Printf("[DEBUG] "+msg, args...) - } -} - -func (l *SimpleLogger) Info(msg string, args ...interface{}) { - l.logger.Printf("[INFO] "+msg, args...) -} - -func (l *SimpleLogger) Warn(msg string, args ...interface{}) { - l.logger.Printf("[WARN] "+msg, args...) -} - -func (l *SimpleLogger) Error(msg string, args ...interface{}) { - l.logger.Printf("[ERROR] "+msg, args...) -} - -func (l *SimpleLogger) Fatal(msg string, args ...interface{}) { - l.logger.Fatalf("[FATAL] "+msg, args...) -} - -// Default logger instance -var Default Logger = NewSimpleLogger(false) - -// SetDefault sets the default logger -func SetDefault(l Logger) { - Default = l -} - -// Convenience functions -func Debug(msg string, args ...interface{}) { - Default.Debug(msg, args...) -} - -func Info(msg string, args ...interface{}) { - Default.Info(msg, args...) -} - -func Warn(msg string, args ...interface{}) { - Default.Warn(msg, args...) -} - -func Error(msg string, args ...interface{}) { - Default.Error(msg, args...) -} - -func Fatal(msg string, args ...interface{}) { - Default.Fatal(msg, args...) -} - -// Debugf formats and logs a debug message -func Debugf(format string, args ...interface{}) { - Debug(format, args...) -} - -// Infof formats and logs an info message -func Infof(format string, args ...interface{}) { - Info(format, args...) -} - -// Warnf formats and logs a warn message -func Warnf(format string, args ...interface{}) { - Warn(format, args...) -} - -// Errorf formats and logs an error message -func Errorf(format string, args ...interface{}) { - Error(format, args...) -} - -// Fatalf formats and logs a fatal message and exits -func Fatalf(format string, args ...interface{}) { - Fatal(fmt.Sprintf(format, args...)) -} - diff --git a/e2e-tests/internal/utils/retry.go b/e2e-tests/internal/utils/retry.go deleted file mode 100644 index 726492c..0000000 --- a/e2e-tests/internal/utils/retry.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "fmt" - "time" -) - -// RetrySec retries a function with exponential backoff up to maxSeconds -func RetrySec(maxSeconds int, fn func() error) error { - start := time.Now() - attempt := 0 - - for { - err := fn() - if err == nil { - return nil - } - - elapsed := time.Since(start) - if elapsed >= time.Duration(maxSeconds)*time.Second { - return fmt.Errorf("timeout after %d seconds: %w", maxSeconds, err) - } - - attempt++ - backoff := time.Duration(attempt) * time.Second - if backoff > 10*time.Second { - backoff = 10 * time.Second - } - - time.Sleep(backoff) - } -} - -// RetryWithTimeout retries a function until timeout -func RetryWithTimeout(timeout time.Duration, fn func() error) error { - start := time.Now() - attempt := 0 - - for { - err := fn() - if err == nil { - return nil - } - - elapsed := time.Since(start) - if elapsed >= timeout { - return fmt.Errorf("timeout after %v: %w", timeout, err) - } - - attempt++ - backoff := time.Duration(attempt) * time.Second - if backoff > 10*time.Second { - backoff = 10 * time.Second - } - - time.Sleep(backoff) - } -} - diff --git a/e2e-tests/pkg/cluster/interface.go b/e2e-tests/pkg/cluster/interface.go deleted file mode 100644 index a94abcb..0000000 --- a/e2e-tests/pkg/cluster/interface.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cluster - -import ( - "context" - - "github.com/deckhouse/sds-e2e-tests/internal/config" - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes/deckhouse" - "github.com/deckhouse/sds-e2e-tests/internal/kubernetes/virtualization" -) - -// Cluster is the main interface for interacting with a Kubernetes cluster -type Cluster interface { - // Name returns the cluster name - Name() string - - // Context returns the context associated with this cluster - Context() context.Context // TODO amarkov: I'm not sure how good it is in Go to store context. Should we rather pass it in each call as usual? - Context should better be passed as an argument, do not keep in in struct. - - // Virtualization returns the virtualization client for VM operations - Virtualization() virtualization.Client // TODO amarkov: Is the VirtualizationClient this one? https://github.com/deckhouse/virtualization/blob/main/api/client/kubeclient/client.go#L53 - - // Deckhouse returns the deckhouse client for module operations - Deckhouse() deckhouse.Client - - // Lifecycle - EnsureReady(ctx context.Context) error - Close() error -} - -// Manager manages cluster lifecycle -type Manager interface { - // GetOrCreate retrieves an existing cluster or creates a new one - GetOrCreate(ctx context.Context, configPath, name string) (Cluster, error) - - // Get retrieves an existing cluster by name - Get(name string) (Cluster, bool) - - // CloseAll closes all managed clusters - CloseAll() error -} - -// Builder builds and provisions new clusters -type Builder interface { - // BuildCluster creates a new nested Kubernetes cluster on the base cluster - BuildCluster(ctx context.Context, baseCluster Cluster, cfg *config.DKPClusterConfig) (Cluster, error) -} diff --git a/e2e-tests/pkg/testkit/cluster.go b/e2e-tests/pkg/testkit/cluster.go deleted file mode 100644 index 355926c..0000000 --- a/e2e-tests/pkg/testkit/cluster.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package testkit - -import ( - "context" - - internalCluster "github.com/deckhouse/sds-e2e-tests/internal/cluster" - "github.com/deckhouse/sds-e2e-tests/internal/config" - "github.com/deckhouse/sds-e2e-tests/pkg/cluster" -) - -// GetCluster returns or creates a cluster for testing -func GetCluster(ctx context.Context, configPath, name string) (cluster.Cluster, error) { - manager := internalCluster.NewManager() - return manager.GetOrCreate(ctx, configPath, name) -} - -// BuildTestCluster builds a new test cluster from configuration -func BuildTestCluster(ctx context.Context, baseCluster cluster.Cluster, dkpCfg *config.DKPClusterConfig) (cluster.Cluster, error) { - cfg := config.Load() - manager := internalCluster.NewManager() - builder := internalCluster.NewBuilder(cfg, manager, nil) // SSH factory would be passed here - return builder.BuildCluster(ctx, baseCluster, dkpCfg) -} - diff --git a/e2e-tests/pkg/testkit/cluster/cluster.go b/e2e-tests/pkg/testkit/cluster/cluster.go new file mode 100644 index 0000000..50fa30a --- /dev/null +++ b/e2e-tests/pkg/testkit/cluster/cluster.go @@ -0,0 +1,211 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cluster + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "gopkg.in/yaml.v3" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/deckhouse/sds-e2e-tests/internal/config" + "github.com/deckhouse/sds-e2e-tests/internal/infrastructure/ssh" +) + +// LoadClusterConfig loads and validates a cluster configuration from a YAML file +// The config file is expected to be in the same directory as the caller (typically the test file) +func LoadClusterConfig(configFilename string) (*config.ClusterDefinition, error) { + // Get the caller's file path (the test file that called this function) + _, callerFile, _, ok := runtime.Caller(1) + if !ok { + return nil, fmt.Errorf("failed to determine caller file path") + } + callerDir := filepath.Dir(callerFile) + yamlConfigPath := filepath.Join(callerDir, configFilename) + + // Read the YAML file + data, err := os.ReadFile(yamlConfigPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file %s: %w", yamlConfigPath, err) + } + + // Parse YAML directly into ClusterDefinition (has custom UnmarshalYAML for root key) + var clusterDef config.ClusterDefinition + if err := yaml.Unmarshal(data, &clusterDef); err != nil { + return nil, fmt.Errorf("failed to parse YAML config: %w", err) + } + + // Validate the configuration + if err := validateClusterConfig(&clusterDef); err != nil { + return nil, fmt.Errorf("config validation failed: %w", err) + } + + return &clusterDef, nil +} + +// validateClusterConfig validates the cluster configuration +func validateClusterConfig(cfg *config.ClusterDefinition) error { + // Validate that at least one master exists + if len(cfg.Masters) == 0 { + return fmt.Errorf("at least one master node is required") + } + + // Validate master nodes + for i, master := range cfg.Masters { + if err := validateNode(master, true); err != nil { + return fmt.Errorf("master[%d] validation failed: %w", i, err) + } + } + + // Validate worker nodes + for i, worker := range cfg.Workers { + if err := validateNode(worker, false); err != nil { + return fmt.Errorf("worker[%d] validation failed: %w", i, err) + } + } + + // Validate setup node if present + if cfg.Setup != nil { + if err := validateNode(*cfg.Setup, false); err != nil { + return fmt.Errorf("setup node validation failed: %w", err) + } + } + + // Validate DKP parameters + dkpParams := cfg.DKPParameters + if dkpParams.PodSubnetCIDR == "" { + return fmt.Errorf("dkpParameters.podSubnetCIDR is required") + } + if dkpParams.ServiceSubnetCIDR == "" { + return fmt.Errorf("dkpParameters.serviceSubnetCIDR is required") + } + if dkpParams.ClusterDomain == "" { + return fmt.Errorf("dkpParameters.clusterDomain is required") + } + if dkpParams.RegistryRepo == "" { + return fmt.Errorf("dkpParameters.registryRepo is required") + } + + return nil +} + +// validateNode validates a single node configuration +func validateNode(node config.ClusterNode, isMaster bool) error { + if node.Hostname == "" { + return fmt.Errorf("hostname is required") + } + + if node.HostType == config.HostTypeVM { + if node.CPU <= 0 { + return fmt.Errorf("CPU must be greater than 0 for VM nodes") + } + if node.RAM <= 0 { + return fmt.Errorf("RAM must be greater than 0 for VM nodes") + } + if node.DiskSize <= 0 { + return fmt.Errorf("diskSize must be greater than 0 for VM nodes") + } + } + + if node.Auth.User == "" { + return fmt.Errorf("auth.user is required") + } + + if node.Auth.Method == config.AuthMethodSSHKey && node.Auth.SSHKey == "" { + return fmt.Errorf("auth.sshKey is required when using ssh-key authentication") + } + + if node.Auth.Method == config.AuthMethodSSHPass && node.Auth.Password == "" { + return fmt.Errorf("auth.password is required when using ssh-password authentication") + } + + return nil +} + +// GetKubeconfig connects to the master node via SSH, retrieves kubeconfig from /etc/kubernetes/admin.conf, +// and returns a rest.Config that can be used with Kubernetes clients. +// If sshClient is provided, it will be used instead of creating a new connection. +// If sshClient is nil, a new connection will be created and closed automatically. +func GetKubeconfig(masterIP, user, keyPath string, sshClient ssh.SSHClient) (*rest.Config, error) { + // Create SSH client if not provided + shouldClose := false + if sshClient == nil { + var err error + sshClient, err = ssh.NewClient(user, masterIP, keyPath) + if err != nil { + return nil, fmt.Errorf("failed to create SSH client: %w", err) + } + shouldClose = true + } + if shouldClose { + defer sshClient.Close() + } + + // Read kubeconfig from /etc/kubernetes/admin.conf + ctx := context.Background() + kubeconfigContent, err := sshClient.Exec(ctx, "sudo cat /etc/kubernetes/admin.conf") + if err != nil { + return nil, fmt.Errorf("failed to read kubeconfig from master: %w", err) + } + + // Get the test file name from the caller + _, callerFile, _, ok := runtime.Caller(1) + if !ok { + return nil, fmt.Errorf("failed to get caller file information") + } + testFileName := strings.TrimSuffix(filepath.Base(callerFile), filepath.Ext(callerFile)) + + // Determine the temp directory path relative to e2e-tests + // callerFile is in tests/ directory, so we go up one level to reach e2e-tests/ + e2eTestsDir := filepath.Join(filepath.Dir(callerFile), "..") + tempDir := filepath.Join(e2eTestsDir, "temp", testFileName) + + // Create temp directory if it doesn't exist + if err := os.MkdirAll(tempDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create temp directory %s: %w", tempDir, err) + } + + // Create kubeconfig file in temp directory + kubeconfigPath := filepath.Join(tempDir, fmt.Sprintf("kubeconfig-%s.yaml", masterIP)) + kubeconfigFile, err := os.Create(kubeconfigPath) + if err != nil { + return nil, fmt.Errorf("failed to create kubeconfig file %s: %w", kubeconfigPath, err) + } + + // Write kubeconfig content to file + if _, err := kubeconfigFile.Write([]byte(kubeconfigContent)); err != nil { + kubeconfigFile.Close() + return nil, fmt.Errorf("failed to write kubeconfig to file: %w", err) + } + if err := kubeconfigFile.Close(); err != nil { + return nil, fmt.Errorf("failed to close kubeconfig file: %w", err) + } + + // Build rest.Config from the kubeconfig file + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, fmt.Errorf("failed to build config from kubeconfig: %w", err) + } + + return config, nil +} diff --git a/e2e-tests/tests/cluster_creation_test.go b/e2e-tests/tests/cluster_creation_test.go index 39099cd..99ed5d0 100644 --- a/e2e-tests/tests/cluster_creation_test.go +++ b/e2e-tests/tests/cluster_creation_test.go @@ -14,132 +14,62 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Need to add test suite for test package -// - package integration import ( + "fmt" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/client-go/rest" + // "github.com/deckhouse/sds-e2e-tests/pkg/cluster" "github.com/deckhouse/sds-e2e-tests/internal/config" - "github.com/deckhouse/sds-e2e-tests/pkg/cluster" - "github.com/deckhouse/sds-e2e-tests/pkg/testkit" + "github.com/deckhouse/sds-e2e-tests/internal/infrastructure/ssh" + "github.com/deckhouse/sds-e2e-tests/pkg/testkit/cluster" ) var _ = Describe("Cluster Creation", func() { var ( - baseCluster cluster.Cluster - testCluster cluster.Cluster - clusterCfg *config.DKPClusterConfig + yamlConfigFilename string = "cluster_creation_test.yml" + baseClusterMasterIP string = "10.0.0.181" + baseClusterUser string = "w-ansible" + baseClusterSSHPrivateKey string = "~/.ssh/aya_rsa" ) BeforeEach(func(ctx SpecContext) { - - // Load base cluster - cfg := config.Load() var err error - baseCluster, err = testkit.GetCluster(ctx, cfg.BaseCluster.KubeConfig, "") - Expect(err).NotTo(HaveOccurred()) + var clusterDefinition *config.ClusterDefinition + var kubeconfig *rest.Config + var sshClient ssh.SSHClient - // Define test cluster configuration - clusterCfg = &config.DKPClusterConfig{ - ClusterDefinition: config.ClusterDefinition{ - Masters: []config.ClusterNode{ - { - Hostname: "master-1", - HostType: config.HostTypeVM, - Role: config.ClusterRoleMaster, - OSType: config.OSTypeMap["Ubuntu 22.04 6.2.0-39-generic"], - Auth: config.NodeAuth{ - Method: config.AuthMethodSSHKey, - User: "user", - SSHKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8WyGvnBNQp+v6CUweF1QYCRtR7Do/IA8IA2uMd2HuBsddFrc5xYon2ZtEvypZC4Vm1CzgcgUm9UkHgxytKEB4zOOWkmqFP62OSLNyuWMaFEW1fb0EDenup6B5SrjnA8ckm4Hf2NSLvwW9yS98TfN3nqPOPJKfQsN+OTiCerTtNyXjca//ppuGKsQd99jG7SqE9aDQ3sYCXatM53SXqhxS2nTew82bmzVmKXDxcIzVrS9f+2WmXIdY2cKo2I352yKWOIp1Nk0uji8ozLPHFQGvbAG8DGG1KNVcBl2qYUcttmCpN+iXEcGqyn/atUVJJMnZXGtp0fiL1rMLqAd/bb6TFNzZFSsS+zqGesxqLePe32vLCQ3xursP3BRZkrScM+JzIqevfP63INHJEZfYlUf4Ic+gfliS2yA1LwhU7hD4LSVXMQynlF9WeGjuv6ZYxmO8hC6IWCqWnIUqKUiGtvBSPXwsZo7wgljBr4ykJgBzS9MjZ0fzz1JKe80tH6clpjIOn6ReBPwQBq2zmDDrpa5GVqqqjXhRQuA0AfpHdhs5UKxs1PBr7/PTLA7PI39xkOAE/Zj1TYQ2dmqvpskshi7AtBStjinQBAlLXysLSHBtO+3+PLAYcMZMVfb0bVqfGGludO2prvXrrWWTku0eOsA5IRahrRdGhv5zhKgFV7cwUQ== ayakubov@MacBook-Pro-Alexey.local", - }, - CPU: 4, - RAM: 8, - DiskSize: 30, - }, - }, - Workers: []config.ClusterNode{ - { - Hostname: "worker-1", - HostType: config.HostTypeVM, - Role: config.ClusterRoleWorker, - OSType: config.OSTypeMap["Ubuntu 22.04 6.2.0-39-generic"], - Auth: config.NodeAuth{ - Method: config.AuthMethodSSHKey, - User: "user", - SSHKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8WyGvnBNQp+v6CUweF1QYCRtR7Do/IA8IA2uMd2HuBsddFrc5xYon2ZtEvypZC4Vm1CzgcgUm9UkHgxytKEB4zOOWkmqFP62OSLNyuWMaFEW1fb0EDenup6B5SrjnA8ckm4Hf2NSLvwW9yS98TfN3nqPOPJKfQsN+OTiCerTtNyXjca//ppuGKsQd99jG7SqE9aDQ3sYCXatM53SXqhxS2nTew82bmzVmKXDxcIzVrS9f+2WmXIdY2cKo2I352yKWOIp1Nk0uji8ozLPHFQGvbAG8DGG1KNVcBl2qYUcttmCpN+iXEcGqyn/atUVJJMnZXGtp0fiL1rMLqAd/bb6TFNzZFSsS+zqGesxqLePe32vLCQ3xursP3BRZkrScM+JzIqevfP63INHJEZfYlUf4Ic+gfliS2yA1LwhU7hD4LSVXMQynlF9WeGjuv6ZYxmO8hC6IWCqWnIUqKUiGtvBSPXwsZo7wgljBr4ykJgBzS9MjZ0fzz1JKe80tH6clpjIOn6ReBPwQBq2zmDDrpa5GVqqqjXhRQuA0AfpHdhs5UKxs1PBr7/PTLA7PI39xkOAE/Zj1TYQ2dmqvpskshi7AtBStjinQBAlLXysLSHBtO+3+PLAYcMZMVfb0bVqfGGludO2prvXrrWWTku0eOsA5IRahrRdGhv5zhKgFV7cwUQ== ayakubov@MacBook-Pro-Alexey.local", - }, - CPU: 2, - RAM: 6, - DiskSize: 30, - }, - { - Hostname: "worker-2", - HostType: config.HostTypeVM, - Role: config.ClusterRoleWorker, - OSType: config.OSTypeMap["Ubuntu 22.04 6.2.0-39-generic"], - Auth: config.NodeAuth{ - Method: config.AuthMethodSSHKey, - User: "user", - SSHKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8WyGvnBNQp+v6CUweF1QYCRtR7Do/IA8IA2uMd2HuBsddFrc5xYon2ZtEvypZC4Vm1CzgcgUm9UkHgxytKEB4zOOWkmqFP62OSLNyuWMaFEW1fb0EDenup6B5SrjnA8ckm4Hf2NSLvwW9yS98TfN3nqPOPJKfQsN+OTiCerTtNyXjca//ppuGKsQd99jG7SqE9aDQ3sYCXatM53SXqhxS2nTew82bmzVmKXDxcIzVrS9f+2WmXIdY2cKo2I352yKWOIp1Nk0uji8ozLPHFQGvbAG8DGG1KNVcBl2qYUcttmCpN+iXEcGqyn/atUVJJMnZXGtp0fiL1rMLqAd/bb6TFNzZFSsS+zqGesxqLePe32vLCQ3xursP3BRZkrScM+JzIqevfP63INHJEZfYlUf4Ic+gfliS2yA1LwhU7hD4LSVXMQynlF9WeGjuv6ZYxmO8hC6IWCqWnIUqKUiGtvBSPXwsZo7wgljBr4ykJgBzS9MjZ0fzz1JKe80tH6clpjIOn6ReBPwQBq2zmDDrpa5GVqqqjXhRQuA0AfpHdhs5UKxs1PBr7/PTLA7PI39xkOAE/Zj1TYQ2dmqvpskshi7AtBStjinQBAlLXysLSHBtO+3+PLAYcMZMVfb0bVqfGGludO2prvXrrWWTku0eOsA5IRahrRdGhv5zhKgFV7cwUQ== ayakubov@MacBook-Pro-Alexey.local", - }, - CPU: 2, - RAM: 6, - DiskSize: 30, - }, - }, - }, - KubernetesVersion: "Automatic", - PodSubnetCIDR: "10.112.0.0/16", - ServiceSubnetCIDR: "10.225.0.0/16", - ClusterDomain: "cluster.local", - LicenseKey: "", - RegistryRepo: "dev-registry.deckhouse.io/sys/deckhouse-oss", - } - }) - - It("should create a nested Kubernetes cluster with all modules enabled", func(ctx SpecContext) { - var err error + // Stage 1: LoadConfig - verifies and parses the config from yaml file + By("LoadConfig: Loading and verifying cluster configuration from YAML", func() { + clusterDefinition, err = cluster.LoadClusterConfig(yamlConfigFilename) + Expect(err).NotTo(HaveOccurred()) + }) - By("Building the test cluster", func() { - testCluster, err = testkit.BuildTestCluster(ctx, baseCluster, clusterCfg) + // Stage 2: Establish SSH connection to base cluster (reused for getting kubeconfig) + By("Establishing ssh connection to the base cluster", func() { + sshClient, err = ssh.NewClient(baseClusterUser, baseClusterMasterIP, baseClusterSSHPrivateKey) Expect(err).NotTo(HaveOccurred()) }) - By("Verifying the cluster is ready", func() { - err = testCluster.EnsureReady(ctx) // TODO - use Eventually instead of Expect - Expect(err).NotTo(HaveOccurred()) // there must be Eventually + // Stage 3: Getting kubeconfig from base cluster (reusing SSH connection to avoid double passphrase prompt) + By("Get kubeconfig: Getting kubeconfig from the base cluster", func() { + kubeconfig, err = cluster.GetKubeconfig(baseClusterMasterIP, baseClusterUser, baseClusterSSHPrivateKey, sshClient) + Expect(err).NotTo(HaveOccurred()) }) - By("Verifying modules are enabled", func() { - // Check that modules are enabled - dhClient := testCluster.Deckhouse() + _ = sshClient // TODO: use sshClient + _ = clusterDefinition // TODO: use clusterDefinition + _ = kubeconfig // TODO: use kubeconfig + }) // BeforeEach: Cluster Creation - modules := []string{ - "snapshot-controller", - "sds-local-volume", - "sds-node-configurator", - "sds-replicated-volume", - } + It("should create a test cluster", func() { + By("Creating a test cluster", func() { + fmt.Println("Creating a test cluster") - for _, moduleName := range modules { - err = dhClient.EnsureModuleEnabled(ctx, &config.ModuleConfig{ - Name: moduleName, - Version: 1, - Enabled: true, - }) - Expect(err).NotTo(HaveOccurred()) - } }) - }) - - AfterEach(func() { - if testCluster != nil { - _ = testCluster.Close() - } - }) -}) + }) // It: should create a test cluster +}) // Describe: Cluster Creation diff --git a/e2e-tests/tests/cluster_creation_test.yml b/e2e-tests/tests/cluster_creation_test.yml new file mode 100644 index 0000000..711e9a3 --- /dev/null +++ b/e2e-tests/tests/cluster_creation_test.yml @@ -0,0 +1,69 @@ +# Test nested cluster config definition +clusterDefinition: + masters: # Master nodes configuration + - hostname: "master-1" + hostType: "vm" + role: "master" + osType: "Ubuntu 22.04 6.2.0-39-generic" + auth: + method: "ssh-key" + user: "user" + sshKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8WyGvnBNQp+v6CUweF1QYCRtR7Do/IA8IA2uMd2HuBsddFrc5xYon2ZtEvypZC4Vm1CzgcgUm9UkHgxytKEB4zOOWkmqFP62OSLNyuWMaFEW1fb0EDenup6B5SrjnA8ckm4Hf2NSLvwW9yS98TfN3nqPOPJKfQsN+OTiCerTtNyXjca//ppuGKsQd99jG7SqE9aDQ3sYCXatM53SXqhxS2nTew82bmzVmKXDxcIzVrS9f+2WmXIdY2cKo2I352yKWOIp1Nk0uji8ozLPHFQGvbAG8DGG1KNVcBl2qYUcttmCpN+iXEcGqyn/atUVJJMnZXGtp0fiL1rMLqAd/bb6TFNzZFSsS+zqGesxqLePe32vLCQ3xursP3BRZkrScM+JzIqevfP63INHJEZfYlUf4Ic+gfliS2yA1LwhU7hD4LSVXMQynlF9WeGjuv6ZYxmO8hC6IWCqWnIUqKUiGtvBSPXwsZo7wgljBr4ykJgBzS9MjZ0fzz1JKe80tH6clpjIOn6ReBPwQBq2zmDDrpa5GVqqqjXhRQuA0AfpHdhs5UKxs1PBr7/PTLA7PI39xkOAE/Zj1TYQ2dmqvpskshi7AtBStjinQBAlLXysLSHBtO+3+PLAYcMZMVfb0bVqfGGludO2prvXrrWWTku0eOsA5IRahrRdGhv5zhKgFV7cwUQ== ayakubov@MacBook-Pro-Alexey.local" + cpu: 4 + ram: 8 + diskSize: 30 + workers: # Worker nodes configuration + - hostname: "worker-1" + hostType: "vm" + role: "worker" + osType: "Ubuntu 22.04 6.2.0-39-generic" + auth: + method: "ssh-key" + user: "user" + sshKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8WyGvnBNQp+v6CUweF1QYCRtR7Do/IA8IA2uMd2HuBsddFrc5xYon2ZtEvypZC4Vm1CzgcgUm9UkHgxytKEB4zOOWkmqFP62OSLNyuWMaFEW1fb0EDenup6B5SrjnA8ckm4Hf2NSLvwW9yS98TfN3nqPOPJKfQsN+OTiCerTtNyXjca//ppuGKsQd99jG7SqE9aDQ3sYCXatM53SXqhxS2nTew82bmzVmKXDxcIzVrS9f+2WmXIdY2cKo2I352yKWOIp1Nk0uji8ozLPHFQGvbAG8DGG1KNVcBl2qYUcttmCpN+iXEcGqyn/atUVJJMnZXGtp0fiL1rMLqAd/bb6TFNzZFSsS+zqGesxqLePe32vLCQ3xursP3BRZkrScM+JzIqevfP63INHJEZfYlUf4Ic+gfliS2yA1LwhU7hD4LSVXMQynlF9WeGjuv6ZYxmO8hC6IWCqWnIUqKUiGtvBSPXwsZo7wgljBr4ykJgBzS9MjZ0fzz1JKe80tH6clpjIOn6ReBPwQBq2zmDDrpa5GVqqqjXhRQuA0AfpHdhs5UKxs1PBr7/PTLA7PI39xkOAE/Zj1TYQ2dmqvpskshi7AtBStjinQBAlLXysLSHBtO+3+PLAYcMZMVfb0bVqfGGludO2prvXrrWWTku0eOsA5IRahrRdGhv5zhKgFV7cwUQ== ayakubov@MacBook-Pro-Alexey.local" + cpu: 2 + ram: 6 + diskSize: 30 + - hostname: "worker-2" + hostType: "vm" + role: "worker" + osType: "Ubuntu 22.04 6.2.0-39-generic" + auth: + method: "ssh-key" + user: "user" + sshKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC8WyGvnBNQp+v6CUweF1QYCRtR7Do/IA8IA2uMd2HuBsddFrc5xYon2ZtEvypZC4Vm1CzgcgUm9UkHgxytKEB4zOOWkmqFP62OSLNyuWMaFEW1fb0EDenup6B5SrjnA8ckm4Hf2NSLvwW9yS98TfN3nqPOPJKfQsN+OTiCerTtNyXjca//ppuGKsQd99jG7SqE9aDQ3sYCXatM53SXqhxS2nTew82bmzVmKXDxcIzVrS9f+2WmXIdY2cKo2I352yKWOIp1Nk0uji8ozLPHFQGvbAG8DGG1KNVcBl2qYUcttmCpN+iXEcGqyn/atUVJJMnZXGtp0fiL1rMLqAd/bb6TFNzZFSsS+zqGesxqLePe32vLCQ3xursP3BRZkrScM+JzIqevfP63INHJEZfYlUf4Ic+gfliS2yA1LwhU7hD4LSVXMQynlF9WeGjuv6ZYxmO8hC6IWCqWnIUqKUiGtvBSPXwsZo7wgljBr4ykJgBzS9MjZ0fzz1JKe80tH6clpjIOn6ReBPwQBq2zmDDrpa5GVqqqjXhRQuA0AfpHdhs5UKxs1PBr7/PTLA7PI39xkOAE/Zj1TYQ2dmqvpskshi7AtBStjinQBAlLXysLSHBtO+3+PLAYcMZMVfb0bVqfGGludO2prvXrrWWTku0eOsA5IRahrRdGhv5zhKgFV7cwUQ== ayakubov@MacBook-Pro-Alexey.local" + cpu: 2 + ram: 6 + diskSize: 30 + # DKP parameters + dkpParameters: + kubernetesVersion: "Automatic" + podSubnetCIDR: "10.112.0.0/16" + serviceSubnetCIDR: "10.225.0.0/16" + clusterDomain: "cluster.local" + licenseKey: "" + registryRepo: "dev-registry.deckhouse.io/sys/deckhouse-oss" + namespace: "e2e-nested-1" + storageClass: "nfs-storage-class" + modules: + - name: "snapshot-controller" + version: 1 + enabled: true + dependencies: [] + - name: "sds-local-volume" + version: 1 + enabled: true + dependencies: + - "snapshot-controller" + - name: "sds-node-configurator" + version: 1 + enabled: true + settings: + enableThinProvisioning: true + dependencies: + - "sds-local-volume" + - name: "sds-replicated-volume" + version: 1 + enabled: true + dependencies: + - "sds-node-configurator" diff --git a/e2e-tests/internal/utils/crypto.go b/e2e-tests/tests/integration_suite_test.go similarity index 73% rename from e2e-tests/internal/utils/crypto.go rename to e2e-tests/tests/integration_suite_test.go index 403f422..f9d903e 100644 --- a/e2e-tests/internal/utils/crypto.go +++ b/e2e-tests/tests/integration_suite_test.go @@ -14,16 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package integration import ( - "crypto/md5" - "fmt" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -// HashMd5 computes MD5 hash of input string and returns hex representation -func HashMd5(input string) string { - hash := md5.Sum([]byte(input)) - return fmt.Sprintf("%x", hash) +func TestIntegration(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Integration Test Suite") } -