ronantakizawa/codereview-bench huggingface dataset:
Repository: istio/istio
File: pkg/test/util/yml/parts.go (Go)

Code before:
		if e := yaml.Unmarshal([]byte(cfg), &typeMeta); e != nil {
			// Ignore invalid parts. This most commonly happens when it's empty or contains only comments.
			continue
		}
		result[typeMeta.Kind] = JoinString(result[typeMeta.Kind], cfg)
	}
	return result
}

// SplitYamlByKind splits the given YAML into parts indexed by kind.
func GetMetadata(content string) []metav1.ObjectMeta {
	cfgs := SplitString(content)
	result := []metav1.ObjectMeta{}
	for _, cfg := range cfgs {
		var m metav1.ObjectMeta
		if e := yaml.Unmarshal([]byte(cfg), &m); e != nil {
			// Ignore invalid parts. This most commonly happens when it's empty or contains only comments.
			continue
		}
		result = append(result, m)
	}
	return result
}

// SplitString splits the given yaml doc if it's multipart document.
func SplitString(yamlText string) []string {
	out := make([]string, 0)
	reader := bufio.NewReader(strings.NewReader(yamlText))

	parts := []string{}
	active := strings.Builder{}

	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err == bufio.ErrBufferFull {
				active.WriteString(line)
				continue
			}
			if line != "" {
				active.WriteString(line)
			}
			break
		}

		if strings.HasPrefix(line, "---") {
			parts = append(parts, active.String())
			active = strings.Builder{}
		} else {
			active.WriteString(line)
		}

Review comment (suggestion):
Not related to this PR but its a bit problematic that the iostioctl is using a package under `test/`...

Code after:
		if e := yaml.Unmarshal([]byte(cfg), &typeMeta); e != nil {
			// Ignore invalid parts. This most commonly happens when it's empty or contains only comments.
			continue
		}
		result[typeMeta.Kind] = JoinString(result[typeMeta.Kind], cfg)
	}
	return result
}

// SplitYamlByKind splits the given YAML into parts indexed by kind.
func GetMetadata(content string) []metav1.ObjectMeta {
	cfgs := SplitString(content)
	result := []metav1.ObjectMeta{}
	for _, cfg := range cfgs {
		var m metav1.ObjectMeta
		if e := yaml.Unmarshal([]byte(cfg), &m); e != nil {
			// Ignore invalid parts. This most commonly happens when it's empty or contains only comments.
			continue
		}
		result = append(result, m)
	}
	return result
}

// SplitString splits the given yaml doc if it's multipart document.
func SplitString(yamlText string) []string {
	out := make([]string, 0)
	reader := bufio.NewReader(strings.NewReader(yamlText))

	parts := []string{}
	active := strings.Builder{}

	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if line != "" {
				active.WriteString(line)
			}
			break
		}

		if strings.HasPrefix(line, "---") {
			parts = append(parts, active.String())
			active = strings.Builder{}
		} else {
			active.WriteString(line)
		}
	}

	if active.Len() > 0 {
		parts = append(parts, active.String())
Repository: istio/istio
File: istioctl/pkg/internaldebug/internal-debug.go (Go)

Code before:
				if err := json.Indent(&out, resource.Value, "", "  "); err != nil {
					// resource.Value is not a json, fallback to raw output
					_, _ = s.Writer.Write(resource.Value)
				} else {
					_, _ = out.WriteTo(os.Stdout)
				}

				_, _ = s.Writer.Write([]byte("\n"))
			}
		}
	}
	if len(mappedResp) > 0 {
		rawMap := make(map[string]json.RawMessage)
		for k, v := range mappedResp {
			var temp interface{}
			if err := json.Unmarshal(v, &temp); err == nil {
				rawMap[k] = json.RawMessage(v)
			} else {
				str := string(v)
				encodedStr, err := json.Marshal(str)
				if err == nil {
					rawMap[k] = json.RawMessage(encodedStr)
				} else {
					// the final fallback, back to string, the theory will not stop here
					rawMap[k] = json.RawMessage(str)
				}
			}
		}
		mresp, err := json.MarshalIndent(rawMap, "", "  ")
		if err != nil {
			return err
		}
		_, _ = s.Writer.Write(mresp)
		_, _ = s.Writer.Write([]byte("\n"))
	}

	return nil
}


Review comment (bug):
I think if we can do

```go
if err != nil {
  return some sor tof error
}
rawMap[k] = json.RawMessage(encodedStr)
```

go should never fail to marshal a string.

Code after:
				if err := json.Indent(&out, resource.Value, "", "  "); err != nil {
					// resource.Value is not a json, fallback to raw output
					_, _ = s.Writer.Write(resource.Value)
				} else {
					_, _ = out.WriteTo(os.Stdout)
				}

				_, _ = s.Writer.Write([]byte("\n"))
			}
		}
	}
	if len(mappedResp) > 0 {
		rawMap := make(map[string]json.RawMessage)
		for k, v := range mappedResp {
			var temp interface{}
			if err := json.Unmarshal(v, &temp); err == nil {
				rawMap[k] = json.RawMessage(v)
			} else {
				str := string(v)
				encodedStr, err := json.Marshal(str)
				if err != nil {
					return err
				}
				rawMap[k] = json.RawMessage(encodedStr)
			}
		}
		mresp, err := json.MarshalIndent(rawMap, "", "  ")
		if err != nil {
			return err
		}
		_, _ = s.Writer.Write(mresp)
		_, _ = s.Writer.Write([]byte("\n"))
	}

	return nil
}
Repository: istio/istio
File: pilot/pkg/config/kube/gateway/backend_policies.go (Go)

Code before:
		if x.ControllerName != ourController {
			// Keep it as-is
			existing[n] = x
			n++
			continue
		}
		replacement := slices.IndexFunc(incoming, func(status gatewayalpha2.PolicyAncestorStatus) bool {
			return parentRefEqual(status.AncestorRef, x.AncestorRef)
		})
		if replacement != -1 {
			// We found a replacement!
			existing[n] = incoming[replacement]
			incoming = slices.Delete(incoming, replacement)
			n++
		}
		// Else, do nothing and it will be filtered
	}
	existing = existing[:n]
	// Add all remaining ones.
	existing = append(existing, incoming...)
	return existing
}

func generateDRName(target TypedNamedspacedName, host string) string {
	if target.Kind == kind.ServiceEntry {
		return target.Name + "-" + strings.ReplaceAll(host, ".", "-") + "-" + constants.KubernetesGatewayName
	}
	return target.Name + "-" + constants.KubernetesGatewayName
}


Review comment (suggestion):
This name is not necessarily safe... since I can have a DR `hi` with host `bar.baz.com` and a DR `hi-bar` with `baz.com`. They both generate the same name

Not sure a great solution though 

Code after:
				conds[string(gw.PolicyConditionAccepted)].error = &ConfigError{
					Reason:  string(gw.PolicyReasonTargetNotFound),
					Message: "targetRefs invalid: " + err.Error(),
				}
			} else {
				// Only create an object if we can resolve the target
				res = append(res, BackendPolicy{
					Source: TypedNamespacedName{
						NamespacedName: config.NamespacedName(i),
						Kind:           kind.XBackendTrafficPolicy,
					},
					TargetIndex: idx,
					Target: TypedNamespacedName{
						NamespacedName: types.NamespacedName{
							Name:      string(t.Name),
							Namespace: i.Namespace,
						},
						Kind: kind.Service,
					},
					Host:         string(t.Name) + "." + i.Namespace + ".svc." + domainSuffix,
					TLS:          nil,
					LoadBalancer: lb,
					RetryBudget:  retryBudget,
					CreationTime: i.CreationTimestamp.Time,
				})
			}

			pr := gw.ParentReference{
				Group: &t.Group,
				Kind:  &t.Kind,
				Name:  t.Name,
			}
			ancestors = append(ancestors, setAncestorStatus(pr, status, i.Generation, conds, constants.ManagedGatewayMeshController))
		}
		status.Ancestors = mergeAncestors(status.Ancestors, ancestors)
		return status, res
	}, opts.WithName("BackendTrafficPolicy")...)
}

func setAncestorStatus(
	pr gw.ParentReference,
	status *gw.PolicyStatus,
	generation int64,
	conds map[string]*condition,
	controller gw.GatewayController,
) gw.PolicyAncestorStatus {
	currentAncestor := slices.FindFunc(status.Ancestors, func(ex gw.PolicyAncestorStatus) bool {
		return parentRefEqual(ex.AncestorRef, pr)
	})
	var currentConds []metav1.Condition
	if currentAncestor != nil {
Repository: istio/istio
File: pkg/kube/krt/join.go (Go)

Code before:
// 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 krt

import (
	"fmt"
	"strconv"
	"sync"

	"istio.io/istio/pkg/kube/controllers"
	"istio.io/istio/pkg/ptr"
	"istio.io/istio/pkg/slices"
	"istio.io/istio/pkg/util/sets"
)

type join[T any] struct {
	collectionName   string
	id               collectionUID
	collections      []internalCollection[T]
	synced           <-chan struct{}
	uncheckedOverlap bool
	syncer           Syncer
	metadata         Metadata
	merge            func(ts []T) *T

	// mergedCache is a cache of the merged results for each key
	// This is used to ensure we have accurate eventing when dealing
	// with merged collections (e.g. that event.Old is set correctly).
	// This will prevent unnecessary xDS pushes; without it, we'd
	// Old != New (merged) would always be true and we'd push.
	mergedCache  map[mergedCacheKey]mergedCacheEntry[T]
	sync.RWMutex // protects mergedCache
}

type mergedCacheKey struct {
	handlerID string // An empty handler id corresponds to the collection itself (e.g. during GetKey() or List())
	key       string
}

type mergedCacheEntry[T any] struct {
	prev    *T
	current *T // Must always be set of there's an entry in the map
}

func (j *join[T]) quickGetKey(k string) *T {
	if j.merge != nil {
		return nil
	}
	for _, c := range j.collections {

Review comment (suggestion):
If you can stomach a copy per element, consider leveraging the new stuff in mergejoin.go; it's far simpler (IMO) and works with nestedjoin merging as well. If you decide to avoid using mergejoin, then this implementation can be _dramatically_ simplified; all of the handler stuff is to deal with dynamic nested collection membership which we don't have here

Code after:
// 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 krt

import (
	"fmt"

	"istio.io/istio/pkg/ptr"
	"istio.io/istio/pkg/slices"
	"istio.io/istio/pkg/util/sets"
)

// TODO: Implement with merging
type join[T any] struct {
	collectionName   string
	id               collectionUID
	collections      []internalCollection[T]
	synced           <-chan struct{}
	uncheckedOverlap bool
	syncer           Syncer
	metadata         Metadata
}

func (j *join[T]) GetKey(k string) *T {
	for _, c := range j.collections {
		if r := c.GetKey(k); r != nil {
			return r
		}
	}
	return nil
}

func (j *join[T]) List() []T {
	var res []T
	if j.uncheckedOverlap {
		first := true
		for _, c := range j.collections {
			objs := c.List()
			// As an optimization, take the first (non-empty) result as-is without copying
			if len(objs) > 0 && first {
				res = objs
				first = false
			} else {
				// After the first, safely merge into the result
				res = append(res, objs...)
			}
		}
		return res
	}
Repository: istio/istio
File: cni/pkg/config/config.go (Go)

Code before:

	// The ztunnel server socket address that the ztunnel will connect to.
	ZtunnelUDSAddress string

	// Whether ambient is enabled
	AmbientEnabled bool

	// The labelSelector to enable ambient for specific pods or namespaces
	AmbientEnablementSelector string

	// Whether ambient DNS capture is enabled
	AmbientDNSCapture bool

	// Whether ipv6 is enabled for ambient capture
	AmbientIPv6 bool

	// Feature flag to disable safe upgrade. Will be removed in future releases.
	AmbientDisableSafeUpgrade bool

	// Whether reconciliation of iptables at post startup is enabled for Ambient workloads
	AmbientReconcilePodRulesOnStartup bool

	// Whether native nftables should be used instead of iptable rules for traffic redirection
	NativeNftables bool

	// Choose the version of iptables to use
	ForceIptablesBinary string
}

// RepairConfig struct defines the Istio CNI race repair configuration
type RepairConfig struct {
	// Whether to enable CNI race repair
	Enabled bool

	// The node name that the CNI DaemonSet runs on
	NodeName string

	// Key and value for broken pod label
	LabelKey   string
	LabelValue string

	// Whether to fix race condition by repairing them
	RepairPods bool

	// Whether to fix race condition by delete broken pods
	DeletePods bool

	// Whether to label broken pods
	LabelPods bool

	// Filters for race repair, including name of sidecar annotation, name of init container,

Review comment (suggestion):
```suggestion
	// Choose which iptables binary to use (legacy or nft)
```

Code after:

	// The ztunnel server socket address that the ztunnel will connect to.
	ZtunnelUDSAddress string

	// Whether ambient is enabled
	AmbientEnabled bool

	// The labelSelector to enable ambient for specific pods or namespaces
	AmbientEnablementSelector string

	// Whether ambient DNS capture is enabled
	AmbientDNSCapture bool

	// Whether ipv6 is enabled for ambient capture
	AmbientIPv6 bool

	// Feature flag to disable safe upgrade. Will be removed in future releases.
	AmbientDisableSafeUpgrade bool

	// Whether reconciliation of iptables at post startup is enabled for Ambient workloads
	AmbientReconcilePodRulesOnStartup bool

	// Whether native nftables should be used instead of iptable rules for traffic redirection
	NativeNftables bool

	// Choose which iptables binary to use (legacy or nft)
	ForceIptablesBinary string
}

// RepairConfig struct defines the Istio CNI race repair configuration
type RepairConfig struct {
	// Whether to enable CNI race repair
	Enabled bool

	// The node name that the CNI DaemonSet runs on
	NodeName string

	// Key and value for broken pod label
	LabelKey   string
	LabelValue string

	// Whether to fix race condition by repairing them
	RepairPods bool

	// Whether to fix race condition by delete broken pods
	DeletePods bool

	// Whether to label broken pods
	LabelPods bool

	// Filters for race repair, including name of sidecar annotation, name of init container,
Repository: istio/istio
File: tools/common/config/validation.go (Go)

Code before:
		return fmt.Errorf("failed to parse CIDR %s: %v", cidr, err)
	}

	if !ipp.Addr().Is4() || !ipp.Addr().IsLoopback() {
		return fmt.Errorf("expected valid IPv4 loopback address in CIDR %s; found %v", cidr, ipp.Addr())
	}

	ones := ipp.Bits()
	if ones < 8 || ones > 32 {
		return fmt.Errorf("expected CIDR %s to have mask in range [8, 32]; found %v", cidr, ones)
	}
	return nil
}

// copied over from the dependency implementation package to avoid circular imports
const (
	legacy = "legacy"
	nft    = "nft"
)

func ValidateIptablesVersion(iptv string) error {
	switch iptv {
	case "", legacy, nft:
		return nil
	default:
		return fmt.Errorf("iptables version %s not supported", iptv)
	}
}


Review comment (suggestion):
```suggestion
		return fmt.Errorf("Invalid FORCE_IPTABLES_BINARY value %q", iptv)
```

Code after:
		return fmt.Errorf("failed to parse CIDR %s: %v", cidr, err)
	}

	if !ipp.Addr().Is4() || !ipp.Addr().IsLoopback() {
		return fmt.Errorf("expected valid IPv4 loopback address in CIDR %s; found %v", cidr, ipp.Addr())
	}

	ones := ipp.Bits()
	if ones < 8 || ones > 32 {
		return fmt.Errorf("expected CIDR %s to have mask in range [8, 32]; found %v", cidr, ones)
	}
	return nil
}

// copied over from the dependency implementation package to avoid circular imports
const (
	legacy = "legacy"
	nft    = "nft"
)

func ValidateIptablesBinary(iptb string) error {
	switch iptb {
	case "", legacy, nft:
		return nil
	default:
		return fmt.Errorf("invalid FORCE_IPTABLES_BINARY value %q", iptb)

	}
}
Repository: istio/istio
File: pkg/kube/krt/mergejoin.go (Go)

Code before:
		}
	}
	if len(found) == 0 {
		return nil
	}
	return j.merge(found)
}

func (j *mergejoin[T]) updateIndexLocked(e Event[T], key Key[T]) {
	switch e.Event {
	case controllers.EventAdd, controllers.EventUpdate:
		for _, index := range j.indexes {
			index.update(e, key)
		}
	case controllers.EventDelete:
		for _, index := range j.indexes {
			index.delete(*e.Old, key)
		}
	}
}

func (j *mergejoin[T]) runQueue(initialCollections []Collection[T], subscriptionFunc func([]Event[T])) {
	// Now that we've subscribed, process the current set of collections.
	regs := []HandlerRegistration{}
	for _, c := range initialCollections {
		// Ensure each sub-collection is synced before we're marked as synced.
		regs = append(regs, c.RegisterBatch(subscriptionFunc, true))
	}

	syncers := slices.Map(regs, func(r HandlerRegistration) cache.InformerSynced {
		return r.HasSynced
	})

	if !kube.WaitForCacheSync(j.collectionName, j.stop, syncers...) {
		return
	}
	j.queue.Run(j.stop)
}

type collections[T any] []Collection[T]

// nolint: unused // (not true, its to implement an interface)
func (jc collections[T]) getCollections() []Collection[T] {
	return jc
}

func JoinWithMergeCollection[T any](cs []Collection[T], merge func(ts []T) *T, opts ...CollectionOption) Collection[T] {
	o := buildCollectionOptions(opts...)
	if o.name == "" {
		o.name = fmt.Sprintf("JoinWithMerge[%v]", ptr.TypeName[T]())
	}

Review comment (suggestion):
This is checking to make sure the event handlers have synced; not the actual collections. This doesn't do a check on initialCollections[].hasSynced, so you probably want to do that first before registering event handlers

Code after:
		}
	}
	if len(found) == 0 {
		return nil
	}
	return j.merge(found)
}

func (j *mergejoin[T]) updateIndexLocked(e Event[T], key Key[T]) {
	switch e.Event {
	case controllers.EventAdd, controllers.EventUpdate:
		for _, index := range j.indexes {
			index.update(e, key)
		}
	case controllers.EventDelete:
		for _, index := range j.indexes {
			index.delete(*e.Old, key)
		}
	}
}

func (j *mergejoin[T]) runQueue() {
	// Wait until all underlying collections are synced before registering
	syncers := slices.Map(j.collections.getCollections(), func(c Collection[T]) cache.InformerSynced {
		return c.HasSynced
	})
	if !kube.WaitForCacheSync(j.collectionName, j.stop, syncers...) {
		return
	}

	// Register with the list of collections
	regs := []HandlerRegistration{}
	for _, c := range j.collections.getCollections() {
		regs = append(regs, c.RegisterBatch(func(events []Event[T]) {
			j.queue.Push(func() error {
				j.onSubCollectionEventHandler(events)
				return nil
			})
		}, true))
	}

	syncers = slices.Map(regs, func(r HandlerRegistration) cache.InformerSynced {
		return r.HasSynced
	})
	if !kube.WaitForCacheSync(j.collectionName, j.stop, syncers...) {
		return
	}

	j.queue.Run(j.stop)
}
Repository: istio/istio
File: pkg/kube/labels/labels.go (Go)

Code before:
	return ok
}

func HasCanonicalServiceRevision(labels map[string]string) bool {
	_, ok := lookupLabelValue(labels, revisionLabels...)
	return ok
}

func canonicalServiceRevision(labels map[string]string) string {
	value, ok := lookupLabelValue(labels, revisionLabels...)
	if !ok {
		return "latest"
	}
	return value
}

func canonicalServiceName(labels map[string]string, workloadName string) string {
	value, ok := lookupLabelValue(labels, nameLabels...)
	if !ok {
		return workloadName
	}
	return value
}

// GetAppName returns the app name from labels, checking the following labels in order:
// 1. service.istio.io/canonical-name
// 2. app.kubernetes.io/name
// 3. app
// Returns the value and true if found, or empty string and false if none of the labels are present.
func GetAppName(labels map[string]string) (string, bool) {
	return lookupLabelValue(labels, nameLabels...)
}


Review comment (suggestion):
For backcompat, I think the order should be reversed. That is `app` should have the highest priority. @sridhargaddam 

Code after:
			return value, true
		}
	}
	return "", false
}

func HasCanonicalServiceName(labels map[string]string) bool {
	_, ok := lookupLabelValue(labels, nameLabels...)
	return ok
}

func HasCanonicalServiceRevision(labels map[string]string) bool {
	_, ok := lookupLabelValue(labels, revisionLabels...)
	return ok
}

func canonicalServiceRevision(labels map[string]string) string {
	value, ok := lookupLabelValue(labels, revisionLabels...)
	if !ok {
		return "latest"
	}
	return value
}

func canonicalServiceName(labels map[string]string, workloadName string) string {
	value, ok := lookupLabelValue(labels, nameLabels...)
	if !ok {
		return workloadName
	}
	return value
}

// GetApp returns the app name from labels using a backward-compatible
// priority order that checks "app" first for backward compatibility:
// 1. app
// 2. app.kubernetes.io/name
// 3. service.istio.io/canonical-name
// This is used in serviceClusterOrDefault to maintain backward compatibility for existing users
// who have both "app" and "app.kubernetes.io/name" labels with different values.
// Returns the value and true if found, or empty string and false if none of the labels are present.
func GetApp(labels map[string]string) (string, bool) {
	return lookupLabelValue(labels, nameLabelsBackwardCompatible...)
}
Repository: istio/istio
File: pilot/pkg/model/jwks_resolver.go (Go)

Code before:
		defaultDialer := &net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}
		transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
			host, _, err := net.SplitHostPort(addr)
			if err != nil {
				return nil, err
			}

			// Resolve the hostname to get IP addresses
			ips, err := net.DefaultResolver.LookupIP(ctx, "ip", host)
			if err != nil {
				return nil, err
			}

			// Check if any resolved IP is in the blocked list
			for _, ip := range ips {
				ipStr := ip.String()
				if features.BlockedIpsInJWKURIs.Contains(ipStr) {
					return nil, fmt.Errorf("jwksURI %q resolved to blocked IP %q", uri, ipStr)
				}
			}

			// Proceed with the actual connection
			return defaultDialer.DialContext(ctx, network, addr)
		}

		client = &http.Client{
			Transport: transport,
			Timeout:   timeout,
		}
	}

	getPublicKey := func() (b []byte, e error) {
		defer func() {
			if e != nil {
				networkFetchFailCounter.Increment()
			} else {
				networkFetchSuccessCounter.Increment()
			}
		}()
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
		if err != nil {
			return nil, err
		}
		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()

Review comment (security):
This isn't quite right. We are doing a DNS resolution, then doing authz on it, then ignoring it and doing anothe DNS resolution.

An attacker can just make the DNS random return <good IP> and <bad IP>. We need to use the result of the IP address from the original resolution

Code after:
		return "", err
	}

	jwksURI, ok := data["jwks_uri"].(string)
	if !ok {
		return "", fmt.Errorf("invalid jwks_uri %v in openID discovery configuration", data["jwks_uri"])
	}

	return jwksURI, nil
}

func (r *JwksResolver) getRemoteContentWithRetry(uri string, retry int, timeout time.Duration) ([]byte, error) {
	u, err := url.Parse(uri)
	if err != nil {
		log.Errorf("Failed to parse %q", uri)
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	client := r.httpClient
	if strings.EqualFold(u.Scheme, "https") {
		// https client may be uninitialized because of root CA bundle missing.
		if r.secureHTTPClient == nil {
			return nil, fmt.Errorf("pilot does not support fetch public key through https endpoint %q", uri)
		}

		client = r.secureHTTPClient
	}

	getPublicKey := func() (b []byte, e error) {
		defer func() {
			if e != nil {
				networkFetchFailCounter.Increment()
			} else {
				networkFetchSuccessCounter.Increment()
			}
		}()
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
		if err != nil {
			return nil, err
		}
		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {