diff --git a/cmd/sveltosctl/main.go b/cmd/sveltosctl/main.go index dae5085..10dd76f 100644 --- a/cmd/sveltosctl/main.go +++ b/cmd/sveltosctl/main.go @@ -121,8 +121,10 @@ Description: err = commands.Generate(ctx, args, logger) case "log-level": err = commands.LogLevel(ctx, args, logger) - case "version": + case "": err = commands.Version(args, logger) + case "redeploy": + err = commands.RedeployCluster(ctx, args, logger) default: err = fmt.Errorf("unknown command: %q\n%s", command, doc) } diff --git a/go.mod b/go.mod index 7fe8641..b3a4b17 100644 --- a/go.mod +++ b/go.mod @@ -7,20 +7,20 @@ require ( github.com/fatih/color v1.18.0 github.com/go-logr/logr v1.4.3 github.com/olekukonko/tablewriter v1.1.2 - github.com/onsi/ginkgo/v2 v2.27.2 - github.com/onsi/gomega v1.38.2 + github.com/onsi/ginkgo/v2 v2.27.3 + github.com/onsi/gomega v1.38.3 github.com/pkg/errors v0.9.1 github.com/projectsveltos/addon-controller v1.1.1-0.20251203094149-5ab010fd58ba github.com/projectsveltos/event-manager v1.1.1-0.20251203131606-c8763831dca6 - github.com/projectsveltos/libsveltos v1.1.1-0.20251203082530-326accb8509d + github.com/projectsveltos/libsveltos v1.1.1-0.20251211094214-0c961e2cd468 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.34.2 - k8s.io/apiextensions-apiserver v0.34.2 - k8s.io/apimachinery v0.34.2 - k8s.io/client-go v0.34.2 + k8s.io/api v0.34.3 + k8s.io/apiextensions-apiserver v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/client-go v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.34.2 - sigs.k8s.io/cluster-api v1.11.3 + k8s.io/kubectl v0.34.3 + sigs.k8s.io/cluster-api v1.12.0 sigs.k8s.io/controller-runtime v0.22.4 sigs.k8s.io/yaml v1.6.0 ) @@ -34,7 +34,7 @@ require ( github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -76,21 +76,21 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.29.0 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.18.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/tools v0.39.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/cluster-bootstrap v0.33.3 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/cluster-bootstrap v0.34.2 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/go.sum b/go.sum index 6b36913..25609ea 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -117,10 +117,10 @@ github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg= github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc= github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg= -github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= -github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8= +github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= +github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -130,8 +130,8 @@ github.com/projectsveltos/addon-controller v1.1.1-0.20251203094149-5ab010fd58ba github.com/projectsveltos/addon-controller v1.1.1-0.20251203094149-5ab010fd58ba/go.mod h1:ckZfeXRkCdpZ6CmrROIGLId3/oaUAPGXm9WHsVam+D4= github.com/projectsveltos/event-manager v1.1.1-0.20251203131606-c8763831dca6 h1:GaenA+OtzxNHJuIWrRVkKZehns4OHYLGsMmnbPSexWQ= github.com/projectsveltos/event-manager v1.1.1-0.20251203131606-c8763831dca6/go.mod h1:hj7dsojqQ27n4APxejlCSyF4rWHMpBSw82xKZIdol90= -github.com/projectsveltos/libsveltos v1.1.1-0.20251203082530-326accb8509d h1:/FSx1qkx/wDGMK2G+aQJhtab+RrIArcislcb95IGMk4= -github.com/projectsveltos/libsveltos v1.1.1-0.20251203082530-326accb8509d/go.mod h1:NHDtt33ROCIo97eJYG675iC7A2c3y6D4WFEJNzALLIM= +github.com/projectsveltos/libsveltos v1.1.1-0.20251211094214-0c961e2cd468 h1:G6DlFDVyIWnuHTPHn8YQJ3ZjWGQd1fmsexAe7etf+D0= +github.com/projectsveltos/libsveltos v1.1.1-0.20251211094214-0c961e2cd468/go.mod h1:l6tv5CGAozsy+YnPro/XjO5m66S3lgqWV889QLm1/zg= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -180,8 +180,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -191,21 +191,21 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -216,24 +216,24 @@ golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +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.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +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= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= @@ -244,28 +244,28 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/cluster-bootstrap v0.33.3 h1:u2NTxJ5CFSBFXaDxLQoOWMly8eni31psVso+caq6uwI= -k8s.io/cluster-bootstrap v0.33.3/go.mod h1:p970f8u8jf273zyQ5raD8WUu2XyAl0SAWOY82o7i/ds= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/cluster-bootstrap v0.34.2 h1:oKckPeunVCns37BntcsxaOesDul32yzGd3DFLjW2fc8= +k8s.io/cluster-bootstrap v0.34.2/go.mod h1:f21byPR7X5nt12ivZi+J3pb4sG4SH6VySX8KAAJA8BY= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= 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-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/kubectl v0.34.2 h1:+fWGrVlDONMUmmQLDaGkQ9i91oszjjRAa94cr37hzqA= -k8s.io/kubectl v0.34.2/go.mod h1:X2KTOdtZZNrTWmUD4oHApJ836pevSl+zvC5sI6oO2YQ= +k8s.io/kubectl v0.34.3 h1:vpM6//153gh5gvsYHXWHVJ4l4xmN5QFwTSmlfd8icm8= +k8s.io/kubectl v0.34.3/go.mod h1:zZQHtIZoUqTP1bAnPzq/3W1jfc0NeOeunFgcswrfg1c= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/cluster-api v1.11.3 h1:apxfugbP1X8AG7THCM74CTarCOW4H2oOc6hlbm1hY80= -sigs.k8s.io/cluster-api v1.11.3/go.mod h1:CA471SACi81M8DzRKTlWpHV33G0cfWEj7sC4fALFVok= +sigs.k8s.io/cluster-api v1.12.0 h1:iFOz8b0LdrMJS5Df1Eb7wyvTkWqlTUM2LHFEHCeI6vA= +sigs.k8s.io/cluster-api v1.12.0/go.mod h1:+S6WJdi8UPdqv5q9nka5al3ed/Qa0zAcSBgzTaa9VKA= sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= diff --git a/hack/tools/go.mod b/hack/tools/go.mod index 3ae936b..9c10a27 100644 --- a/hack/tools/go.mod +++ b/hack/tools/go.mod @@ -3,9 +3,9 @@ module github.com/projectsveltos/sveltosctl/hack/tools go 1.25.5 require ( - github.com/onsi/ginkgo/v2 v2.27.2 - golang.org/x/oauth2 v0.33.0 - k8s.io/client-go v0.34.2 + github.com/onsi/ginkgo/v2 v2.27.3 + golang.org/x/oauth2 v0.34.0 + k8s.io/client-go v0.34.3 sigs.k8s.io/controller-tools v0.19.0 sigs.k8s.io/kind v0.30.0 ) @@ -56,9 +56,9 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect k8s.io/apiextensions-apiserver v0.34.0 // indirect - k8s.io/apimachinery v0.34.2 // indirect + k8s.io/apimachinery v0.34.3 // indirect k8s.io/code-generator v0.34.0 // indirect k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/hack/tools/go.sum b/hack/tools/go.sum index bce52ff..0e4094e 100644 --- a/hack/tools/go.sum +++ b/hack/tools/go.sum @@ -117,8 +117,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= -github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8= +github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -205,8 +205,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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= @@ -263,16 +263,16 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/code-generator v0.34.0 h1:Ze2i1QsvUprIlX3oHiGv09BFQRLCz+StA8qKwwFzees= k8s.io/code-generator v0.34.0/go.mod h1:Py2+4w2HXItL8CGhks8uI/wS3Y93wPKO/9mBQUYNua0= k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= diff --git a/internal/commands/redeploy.go b/internal/commands/redeploy.go new file mode 100644 index 0000000..cd7e49c --- /dev/null +++ b/internal/commands/redeploy.go @@ -0,0 +1,84 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +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 commands + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + + docopt "github.com/docopt/docopt-go" + "github.com/go-logr/logr" + + logs "github.com/projectsveltos/libsveltos/lib/logsettings" + "github.com/projectsveltos/sveltosctl/internal/commands/redeploy" +) + +// RedeployCluster defines the command structure for forcing a redeployment of +// all applications and add-ons on a target cluster. +// +// This function clears the Status field of all relevant ClusterSummary instances, +// forcing Sveltos controllers to treat the deployed add-ons as requiring a fresh +// reconciliation. This process effectively triggers a re-evaluation and re-application +// of all associated ClusterProfiles/Profiles and the resources (configurations, secrets, +// deployments, etc.) they define on the target cluster. +func RedeployCluster(ctx context.Context, args []string, logger logr.Logger) error { + doc := `Usage: + sveltosctl redeploy [...] + + cluster Force a full re-evaluation and redeployment of add-ons and resources on a cluster. + +Options: + -h --help Show this screen. + +Description: + See 'sveltosctl redeploy --help' to read about a specific subcommand. + ` + + parser := &docopt.Parser{ + HelpHandler: docopt.PrintHelpAndExit, + OptionsFirst: true, + SkipHelpFlags: false, + } + + opts, err := parser.ParseArgs(doc, nil, "1.0") + if err != nil { + var userError docopt.UserError + if errors.As(err, &userError) { + logger.V(logs.LogInfo).Info(fmt.Sprintf( + "Invalid option: 'sveltosctl %s'. Use flag '--help' to read about a specific subcommand.\n", + strings.Join(os.Args[1:], " "), + )) + } + os.Exit(1) + } + + command := opts[""].(string) + arguments := append([]string{"logLevel", command}, opts[""].([]string)...) + + switch command { + case clusterCommand: + return redeploy.ForceDeployment(ctx, arguments, logger) + default: + //nolint: forbidigo // print doc + fmt.Println(doc) + } + + return nil +} diff --git a/internal/commands/redeploy/export_test.go b/internal/commands/redeploy/export_test.go new file mode 100644 index 0000000..92dc419 --- /dev/null +++ b/internal/commands/redeploy/export_test.go @@ -0,0 +1,22 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +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 redeploy + +var ( + ResetClusterSummaryInstance = resetClusterSummaryInstance + GetClusterSummariesInOrder = getClusterSummariesInOrder +) diff --git a/internal/commands/redeploy/force_deployment.go b/internal/commands/redeploy/force_deployment.go new file mode 100644 index 0000000..18a6582 --- /dev/null +++ b/internal/commands/redeploy/force_deployment.go @@ -0,0 +1,286 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +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 redeploy + +import ( + "context" + "flag" + "fmt" + "strings" + + "github.com/docopt/docopt-go" + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/client" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" + logs "github.com/projectsveltos/libsveltos/lib/logsettings" + "github.com/projectsveltos/sveltosctl/internal/utils" +) + +// ForceDeployment initiates a cluster-wide redeployment of all add-ons and applications +// managed by Sveltos for the target cluster. +// +// This function internally clears the Status field of all relevant ClusterSummary +// instances associated with the SveltosCluster resource. Clearing the status forces +// the Sveltos controllers to re-evaluate and re-apply all deployed resources, +// treating them as requiring a fresh reconciliation, thus bypassing content hashing +// checks. This action is irreversible once executed. +func ForceDeployment(ctx context.Context, args []string, logger logr.Logger) error { + doc := `Usage: + sveltosctl redeploy cluster [options] --namespace= --cluster= --cluster-type= [--verbose] + + --namespace= Specifies the namespace where the Cluster resource is located. + --cluster= Defines the name of the target cluster to force redeployment on. + --cluster-type= Specifies the type of cluster. Accepted values are 'Capi' and 'Sveltos'. + +Options: + -h --help Show this screen. + --verbose Verbose mode. Print each step. + +Description: + The 'sveltosctl redeploy cluster' command forces Sveltos to re-apply all + configured add-ons and resources for the specified cluster. This is achieved by + resetting the cluster's internal reconciliation status, compelling the Addon Controller + to immediately re-process all associated ClusterProfile/Profile configurations. + + Use this command to trigger a rolling update or configuration re-application + without making any changes to the ClusterProfile/Profile Spec. +` + parsedArgs, err := docopt.ParseArgs(doc, nil, "1.0") + if err != nil { + logger.V(logs.LogInfo).Error(err, "failed to parse args") + return fmt.Errorf( + "invalid option: 'sveltosctl %s'. Use flag '--help' to read about a specific subcommand. Error: %w", + strings.Join(args, " "), + err, + ) + } + if len(parsedArgs) == 0 { + return nil + } + + _ = flag.Lookup("v").Value.Set(fmt.Sprint(logs.LogInfo)) + verbose := parsedArgs["--verbose"].(bool) + if verbose { + err = flag.Lookup("v").Value.Set(fmt.Sprint(logs.LogDebug)) + if err != nil { + return err + } + } + + namespace := "" + if passedNamespace := parsedArgs["--namespace"]; passedNamespace != nil { + namespace = passedNamespace.(string) + } + + cluster := "" + if passedCluster := parsedArgs["--cluster"]; passedCluster != nil { + cluster = passedCluster.(string) + } + + var clusterType libsveltosv1beta1.ClusterType + if passedClusterType := parsedArgs["--cluster-type"]; passedClusterType != nil { + switch passedClusterType { + case string(libsveltosv1beta1.ClusterTypeCapi): + clusterType = libsveltosv1beta1.ClusterTypeCapi + case string(libsveltosv1beta1.ClusterTypeSveltos): + clusterType = libsveltosv1beta1.ClusterTypeSveltos + default: + return fmt.Errorf("invalid cluster type: %s. Accepted values are '%s' and '%s'", + passedClusterType, + libsveltosv1beta1.ClusterTypeCapi, + libsveltosv1beta1.ClusterTypeSveltos) + } + } + + if namespace == "" || cluster == "" { + return fmt.Errorf("both --namespace and --cluster must be specified") + } + + return resetClusterSummaryInstance(ctx, namespace, cluster, &clusterType, logger) +} + +// resetClusterSummaryInstance finds all ClusterSummary resources associated with +// the given cluster and resets their Status field to force a full redeployment. +func resetClusterSummaryInstance(ctx context.Context, namespace, cluster string, + clusterType *libsveltosv1beta1.ClusterType, logger logr.Logger) error { + + logger.V(logs.LogDebug).Info( + "Preparing to force redeployment by resetting ClusterSummary statuses", + "Namespace", namespace, "Cluster", cluster, + ) + + // 1. Get the Kubernetes Client + // Access the initialized client based on the user's kubeconfig context. + instance := utils.GetAccessInstance() + c := instance.GetClient() + + // Log if the client is not initialized (shouldn't happen if sveltosctl is set up correctly) + if c == nil { + return fmt.Errorf("failed to get Kubernetes client: client is not initialized") + } + // 2. Get ClusterSummaries in Dependency Order + resetOrder, csMap, err := getClusterSummariesInOrder(ctx, c, namespace, cluster, clusterType) + if err != nil { + return err + } + + if len(resetOrder) == 0 { + logger.V(logs.LogDebug).Info("No ClusterSummary instances found matching the cluster criteria. Nothing to reset.") + return nil + } + + logger.V(logs.LogDebug).Info("ClusterSummary reset order determined", "Order", resetOrder) + + // 3. Execute the Status Reset in the Determined Order + return performStatusReset(ctx, c, resetOrder, csMap, logger) +} + +// getClusterSummariesInOrder lists all relevant ClusterSummary instances, +// constructs a dependency graph based on Spec.DependsOn, performs a topological +// sort, and returns the list of names in the required reset order along with +// a map of the objects. +// +// The order is determined such that if B depends on A, A is reset before B. +func getClusterSummariesInOrder(ctx context.Context, c client.Client, namespace, cluster string, + clusterType *libsveltosv1beta1.ClusterType, +) (resetOrder []string, csMap map[string]*configv1beta1.ClusterSummary, err error) { + + // 2. List all ClusterSummary resources + clusterSummaryList := &configv1beta1.ClusterSummaryList{} + + listOptions := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels{ + configv1beta1.ClusterNameLabel: cluster, + configv1beta1.ClusterTypeLabel: string(*clusterType), + }, + } + + if err := c.List(ctx, clusterSummaryList, listOptions...); err != nil { + return nil, nil, fmt.Errorf("failed to list ClusterSummary instances: %w", err) + } + + if len(clusterSummaryList.Items) == 0 { + return nil, nil, nil + } + + // --- Graph Construction --- + + // Map: CS Name -> Set of CS Names that it depends on (outgoing dependencies) + dependencies := make(map[string]map[string]bool) + // Map: CS Name -> Pointer to the actual ClusterSummary object + csMap = make(map[string]*configv1beta1.ClusterSummary) + + // Initialize maps and populate dependencies + for i := range clusterSummaryList.Items { + cs := &clusterSummaryList.Items[i] + csMap[cs.Name] = cs + dependencies[cs.Name] = make(map[string]bool) + } + + for i := range clusterSummaryList.Items { + cs := &clusterSummaryList.Items[i] + for j := range cs.Spec.ClusterProfileSpec.DependsOn { + depName := cs.Spec.ClusterProfileSpec.DependsOn[j] + + // Only consider dependencies that are within the currently listed set (i.e., local to this cluster) + if _, exists := csMap[depName]; exists { + dependencies[cs.Name][depName] = true + } + } + } + + // --- Topological Sort (Kahn's Algorithm for Reset Order) --- + + // Queue for resources with zero *OUTGOING* dependencies (the last items in the chain) + queue := []string{} + // Map of outgoing dependencies count + outgoingCount := make(map[string]int) + + for name, outgoingDeps := range dependencies { + outgoingCount[name] = len(outgoingDeps) + if len(outgoingDeps) == 0 { + queue = append(queue, name) + } + } + + resetOrder = []string{} + + for len(queue) > 0 { + csName := queue[0] + queue = queue[1:] + + resetOrder = append(resetOrder, csName) + + // Find all ClusterSummaries that depended *on* this one (incoming dependencies) + for dependentName, dependentDeps := range dependencies { + // Check if 'dependentName' depends on 'csName' + if _, ok := dependentDeps[csName]; ok { + // This means dependentName relies on csName. + // We are removing csName, so dependentName loses one dependency. + outgoingCount[dependentName]-- + + if outgoingCount[dependentName] == 0 { + queue = append(queue, dependentName) + } + } + } + } + + if len(resetOrder) != len(csMap) { + // Cycle detected + return nil, nil, + fmt.Errorf( + "dependency cycle detected in ClusterSummary resources for cluster %s/%s. Cannot proceed with safe redeployment", + namespace, cluster) + } + + return resetOrder, csMap, nil +} + +// performStatusReset iterates through the ClusterSummary resources in the provided +// order and clears their Status field via a Patch operation. +func performStatusReset(ctx context.Context, c client.Client, resetOrder []string, + csMap map[string]*configv1beta1.ClusterSummary, logger logr.Logger) error { + + for _, csName := range resetOrder { + cs := csMap[csName] + + // Create an empty ClusterSummaryStatus to patch + emptyStatus := configv1beta1.ClusterSummaryStatus{} + + // Use Patch to clear only the status field + // We use DeepCopy() to ensure the object passed to MergeFrom is the original state. + patch := client.MergeFrom(cs.DeepCopy()) + cs.Status = emptyStatus + + logger.V(logs.LogDebug).Info("Attempting to patch ClusterSummary status", "ClusterSummary", cs.Name) + + if err := c.Status().Patch(ctx, cs, patch); err != nil { + return fmt.Errorf("failed to patch ClusterSummary %s/%s status: %w", cs.Namespace, cs.Name, err) + } + + logger.V(logs.LogDebug).Info("Successfully reset status to force redeployment", "ClusterSummary", cs.Name) + } + + logger.V(logs.LogDebug).Info( + "Force redeployment initiated successfully by clearing all relevant ClusterSummary statuses in dependency order.", + ) + return nil +} diff --git a/internal/commands/redeploy/force_deployment_test.go b/internal/commands/redeploy/force_deployment_test.go new file mode 100644 index 0000000..1436be8 --- /dev/null +++ b/internal/commands/redeploy/force_deployment_test.go @@ -0,0 +1,206 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +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 redeploy_test + +import ( + "context" + + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2/textlogger" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" + "github.com/projectsveltos/sveltosctl/internal/commands/redeploy" + "github.com/projectsveltos/sveltosctl/internal/utils" +) + +var _ = Describe("Redeploy cluster", func() { + var logger logr.Logger + + BeforeEach(func() { + logger = textlogger.NewLogger(textlogger.NewConfig()) + }) + + It("resetClusterSummaryInstance resets ClusterSummary Status", func() { + clusterNamespace := randomString() + clusterName := randomString() + clusterType := libsveltosv1beta1.ClusterTypeSveltos + + clusterSummary := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Namespace: clusterNamespace, + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + Status: configv1beta1.ClusterSummaryStatus{ + FeatureSummaries: []configv1beta1.FeatureSummary{ + { + FeatureID: libsveltosv1beta1.FeatureResources, + Status: libsveltosv1beta1.FeatureStatusProvisioned, + Hash: []byte(randomString()), + }, + { + FeatureID: libsveltosv1beta1.FeatureHelm, + Status: libsveltosv1beta1.FeatureStatusProvisioned, + Hash: []byte(randomString()), + }, + }, + }, + } + + clusterSummary2 := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Namespace: clusterNamespace, + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName + randomString(), + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + Status: configv1beta1.ClusterSummaryStatus{ + FeatureSummaries: []configv1beta1.FeatureSummary{ + { + FeatureID: libsveltosv1beta1.FeatureResources, + Status: libsveltosv1beta1.FeatureStatusProvisioned, + Hash: []byte(randomString()), + }, + { + FeatureID: libsveltosv1beta1.FeatureHelm, + Status: libsveltosv1beta1.FeatureStatusProvisioned, + Hash: []byte(randomString()), + }, + }, + }, + } + + initObjects := []client.Object{clusterSummary, clusterSummary2} + + scheme, err := utils.GetScheme() + Expect(err).To(BeNil()) + c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). + WithObjects(initObjects...).Build() + utils.InitalizeManagementClusterAcces(scheme, nil, nil, c) + + Expect(redeploy.ResetClusterSummaryInstance(context.TODO(), clusterNamespace, clusterName, + &clusterType, logger)).To(Succeed()) + + currentClusterSummary := &configv1beta1.ClusterSummary{} + Expect(c.Get(context.TODO(), + types.NamespacedName{Namespace: clusterNamespace, Name: clusterSummary.Name}, + currentClusterSummary)).To(Succeed()) + Expect(len(currentClusterSummary.Status.FeatureSummaries)).To(Equal(0)) + + // ClusterSummary instances for other clusters are not reset + Expect(c.Get(context.TODO(), + types.NamespacedName{Namespace: clusterNamespace, Name: clusterSummary2.Name}, + currentClusterSummary)).To(Succeed()) + Expect(len(currentClusterSummary.Status.FeatureSummaries)).To(Equal(2)) + }) + + It("getClusterSummariesInOrder returns ClusterSummary in right order based on dependsOn", func() { + clusterNamespace := randomString() + clusterName := randomString() + clusterType := libsveltosv1beta1.ClusterTypeSveltos + + clusterSummary1 := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Namespace: clusterNamespace, + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + } + + clusterSummary2 := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Namespace: clusterNamespace, + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + Spec: configv1beta1.ClusterSummarySpec{ + ClusterProfileSpec: configv1beta1.Spec{ + DependsOn: []string{clusterSummary1.Name}, + }, + }, + } + + clusterSummary3 := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Namespace: clusterNamespace, + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + Spec: configv1beta1.ClusterSummarySpec{ + ClusterProfileSpec: configv1beta1.Spec{ + DependsOn: []string{clusterSummary2.Name}, + }, + }, + } + + clusterSummary4 := &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Namespace: clusterNamespace, + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + Spec: configv1beta1.ClusterSummarySpec{ + ClusterProfileSpec: configv1beta1.Spec{ + DependsOn: []string{clusterSummary2.Name, clusterSummary3.Name}, + }, + }, + } + + initObjects := []client.Object{clusterSummary3, clusterSummary2, clusterSummary1, clusterSummary4} + + scheme, err := utils.GetScheme() + Expect(err).To(BeNil()) + c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). + WithObjects(initObjects...).Build() + utils.InitalizeManagementClusterAcces(scheme, nil, nil, c) + + resetOrder, csMap, err := redeploy.GetClusterSummariesInOrder(context.TODO(), c, + clusterNamespace, clusterName, &clusterType) + Expect(err).To(BeNil()) + + Expect(len(csMap)).To(Equal(4)) + Expect(len(resetOrder)).To(Equal(4)) + Expect(resetOrder[0]).To(Equal(clusterSummary1.Name)) + Expect(resetOrder[1]).To(Equal(clusterSummary2.Name)) + Expect(resetOrder[2]).To(Equal(clusterSummary3.Name)) + Expect(resetOrder[3]).To(Equal(clusterSummary4.Name)) + }) +}) diff --git a/internal/commands/redeploy/suite_test.go b/internal/commands/redeploy/suite_test.go new file mode 100644 index 0000000..2885017 --- /dev/null +++ b/internal/commands/redeploy/suite_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +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 redeploy_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "sigs.k8s.io/cluster-api/util" +) + +func TestShow(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Show Suite") +} + +func randomString() string { + const length = 10 + return util.RandomString(length) +}