mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-19 22:21:48 +00:00
Replace SempClient implementation
This commit is contained in:
parent
8263ad9d5f
commit
3bbf1cca71
4 changed files with 104 additions and 246 deletions
|
|
@ -1,220 +0,0 @@
|
||||||
package messaging
|
|
||||||
|
|
||||||
// terraform-provider-solacebroker
|
|
||||||
//
|
|
||||||
// Copyright 2024 Solace Corporation. 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.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrResourceNotFound = errors.New("resource not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
var firstRequest = true
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
*retryablehttp.Client
|
|
||||||
url string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
bearerToken string
|
|
||||||
retries uint
|
|
||||||
retryMinInterval time.Duration
|
|
||||||
retryMaxInterval time.Duration
|
|
||||||
requestMinInterval time.Duration
|
|
||||||
requestTimeout time.Duration
|
|
||||||
rateLimiter <-chan time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(*Client)
|
|
||||||
|
|
||||||
func BasicAuth(username, password string) Option {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.username = username
|
|
||||||
client.password = password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//func BearerToken(bearerToken string) Option {
|
|
||||||
// return func(client *Client) {
|
|
||||||
// client.bearerToken = bearerToken
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func Retries(numRetries uint, retryMinInterval, retryMaxInterval time.Duration) Option {
|
|
||||||
// return func(client *Client) {
|
|
||||||
// client.retries = numRetries
|
|
||||||
// client.retryMinInterval = retryMinInterval
|
|
||||||
// client.retryMaxInterval = retryMaxInterval
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func RequestLimits(requestTimeoutDuration, requestMinInterval time.Duration) Option {
|
|
||||||
// return func(client *Client) {
|
|
||||||
// client.requestTimeout = requestTimeoutDuration
|
|
||||||
// client.requestMinInterval = requestMinInterval
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
func NewClient(url string, insecure_skip_verify bool, providerClient bool, options ...Option) *Client {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure_skip_verify},
|
|
||||||
MaxIdleConnsPerHost: 10,
|
|
||||||
}
|
|
||||||
retryClient := retryablehttp.NewClient()
|
|
||||||
retryClient.HTTPClient.Transport = tr
|
|
||||||
if !providerClient {
|
|
||||||
retryClient.Logger = nil
|
|
||||||
}
|
|
||||||
client := &Client{
|
|
||||||
Client: retryClient,
|
|
||||||
url: url,
|
|
||||||
retries: 10, // default 3
|
|
||||||
retryMinInterval: time.Second,
|
|
||||||
retryMaxInterval: time.Second * 10,
|
|
||||||
}
|
|
||||||
for _, o := range options {
|
|
||||||
o(client)
|
|
||||||
}
|
|
||||||
client.Client.RetryMax = int(client.retries)
|
|
||||||
client.Client.RetryWaitMin = client.retryMinInterval
|
|
||||||
client.Client.RetryWaitMax = client.retryMaxInterval
|
|
||||||
client.HTTPClient.Timeout = client.requestTimeout
|
|
||||||
client.HTTPClient.Jar, _ = cookiejar.New(nil)
|
|
||||||
if client.requestMinInterval > 0 {
|
|
||||||
client.rateLimiter = time.NewTicker(client.requestMinInterval).C
|
|
||||||
} else {
|
|
||||||
ch := make(chan time.Time)
|
|
||||||
// closing the channel will make receiving from the channel non-blocking (the value received will be the
|
|
||||||
// zero value)
|
|
||||||
close(ch)
|
|
||||||
client.rateLimiter = ch
|
|
||||||
}
|
|
||||||
firstRequest = true
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RequestWithBody(ctx context.Context, method, url string, body any) (map[string]any, error) {
|
|
||||||
data, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
request, err := http.NewRequestWithContext(ctx, method, c.url+url, bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
slog.Debug(fmt.Sprintf("===== %v to %v =====", request.Method, request.URL))
|
|
||||||
rawBody, err := c.doRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return parseResponseAsObject(ctx, request, rawBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) doRequest(request *http.Request) ([]byte, error) {
|
|
||||||
if !firstRequest {
|
|
||||||
// the value doesn't matter, it is waiting for the value that matters
|
|
||||||
<-c.rateLimiter
|
|
||||||
} else {
|
|
||||||
// only skip rate limiter for the first request
|
|
||||||
firstRequest = false
|
|
||||||
}
|
|
||||||
if request.Method != http.MethodGet {
|
|
||||||
request.Header.Set("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
// Prefer OAuth even if Basic Auth credentials provided
|
|
||||||
if c.bearerToken != "" {
|
|
||||||
request.Header.Set("Authorization", "Bearer "+c.bearerToken)
|
|
||||||
} else if c.username != "" {
|
|
||||||
request.SetBasicAuth(c.username, c.password)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("either username or bearer token must be provided to access the broker")
|
|
||||||
}
|
|
||||||
var response *http.Response
|
|
||||||
var err error
|
|
||||||
response, err = c.StandardClient().Do(request)
|
|
||||||
if err != nil || response == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
rawBody, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil || (response.StatusCode != http.StatusOK && response.StatusCode != http.StatusBadRequest) {
|
|
||||||
return nil, fmt.Errorf("could not perform request: status %v (%v) during %v to %v, response body:\n%s", response.StatusCode, response.Status, request.Method, request.URL, rawBody)
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(io.Discard, response.Body); err != nil {
|
|
||||||
return nil, fmt.Errorf("response processing error: during %v to %v", request.Method, request.URL)
|
|
||||||
}
|
|
||||||
return rawBody, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseResponseAsObject(_ context.Context, request *http.Request, dataResponse []byte) (map[string]any, error) {
|
|
||||||
data := map[string]any{}
|
|
||||||
err := json.Unmarshal(dataResponse, &data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse response body from %v to %v, response body was:\n%s", request.Method, request.URL, dataResponse)
|
|
||||||
}
|
|
||||||
rawData, ok := data["data"]
|
|
||||||
if ok {
|
|
||||||
// Valid data
|
|
||||||
data, _ = rawData.(map[string]any)
|
|
||||||
return data, nil
|
|
||||||
} else {
|
|
||||||
// Analize response metadata details
|
|
||||||
rawData, ok = data["meta"]
|
|
||||||
if ok {
|
|
||||||
data, _ = rawData.(map[string]any)
|
|
||||||
if data["responseCode"].(float64) == http.StatusOK {
|
|
||||||
// this is valid response for delete
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
description := data["error"].(map[string]interface{})["description"].(string)
|
|
||||||
status := data["error"].(map[string]interface{})["status"].(string)
|
|
||||||
if status == "NOT_FOUND" {
|
|
||||||
// resource not found is a special type we want to return
|
|
||||||
return nil, fmt.Errorf("request failed from %v to %v, %v, %v, %w", request.Method, request.URL, description, status, ErrResourceNotFound)
|
|
||||||
}
|
|
||||||
slog.Error(fmt.Sprintf("SEMP request returned %v, %v", description, status))
|
|
||||||
return nil, fmt.Errorf("request failed for %v using %v, %v, %v", request.URL, request.Method, description, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not parse response details from %v to %v, response body was:\n%s", request.Method, request.URL, dataResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RequestWithoutBody(ctx context.Context, method, url string) (map[string]interface{}, error) {
|
|
||||||
request, err := http.NewRequestWithContext(ctx, method, c.url+url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
slog.Debug(fmt.Sprintf("===== %v to %v =====", request.Method, request.URL))
|
|
||||||
rawBody, err := c.doRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return parseResponseAsObject(ctx, request, rawBody)
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
package messaging
|
package messaging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
"github.com/testcontainers/testcontainers-go"
|
"github.com/testcontainers/testcontainers-go"
|
||||||
"github.com/testcontainers/testcontainers-go/wait"
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -18,6 +22,98 @@ const (
|
||||||
AmqpQueuePrefix = "queue://"
|
AmqpQueuePrefix = "queue://"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrResourceNotFound = errors.New("resource not found")
|
||||||
|
|
||||||
|
type SempClient struct {
|
||||||
|
client http.Client
|
||||||
|
sempApiBaseUrl string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c SempClient) RequestWithoutBody(ctx context.Context, method string, url string) error {
|
||||||
|
request, err := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s%s", c.sempApiBaseUrl, url), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.doRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.parseResponseAsObject(response)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c SempClient) RequestWithBody(ctx context.Context, method string, url string, body any) error {
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s%s", c.sempApiBaseUrl, url), bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.doRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.parseResponseAsObject(response)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c SempClient) doRequest(request *http.Request) ([]byte, error) {
|
||||||
|
request.SetBasicAuth(c.username, c.password)
|
||||||
|
if request.Method != http.MethodGet {
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
response, err := c.client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
rawBody, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil || (response.StatusCode != http.StatusOK && response.StatusCode != http.StatusBadRequest) {
|
||||||
|
return nil, fmt.Errorf("request to %v failes with status %v (%v), response:\n%s", response.StatusCode, response.Status, request.URL, rawBody)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(io.Discard, response.Body); err != nil {
|
||||||
|
return nil, fmt.Errorf("response processing error for call to %v", request.URL)
|
||||||
|
}
|
||||||
|
return rawBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c SempClient) parseResponseAsObject(dataResponse []byte) (map[string]any, error) {
|
||||||
|
data := map[string]any{}
|
||||||
|
err := json.Unmarshal(dataResponse, &data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse response:\n%s", dataResponse)
|
||||||
|
}
|
||||||
|
rawData, ok := data["data"]
|
||||||
|
if ok {
|
||||||
|
data, _ = rawData.(map[string]any)
|
||||||
|
return data, nil
|
||||||
|
} else {
|
||||||
|
metadata, ok := data["meta"]
|
||||||
|
if ok {
|
||||||
|
data, _ = metadata.(map[string]any)
|
||||||
|
if data["responseCode"].(float64) == http.StatusOK {
|
||||||
|
// http-delete
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
description := data["error"].(map[string]interface{})["description"].(string)
|
||||||
|
status := data["error"].(map[string]interface{})["status"].(string)
|
||||||
|
if status == "NOT_FOUND" {
|
||||||
|
// resource not found
|
||||||
|
return nil, fmt.Errorf("request failed - description: %v, status: %v, %w", description, status, ErrResourceNotFound)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed - description: %v, status: %v", description, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not parse response:\n%s", dataResponse)
|
||||||
|
}
|
||||||
|
|
||||||
// SolaceContainer wraps a testcontainers docker container instance of solace.
|
// SolaceContainer wraps a testcontainers docker container instance of solace.
|
||||||
//
|
//
|
||||||
// The container must be terminated by calling:
|
// The container must be terminated by calling:
|
||||||
|
|
@ -25,7 +121,7 @@ const (
|
||||||
type SolaceContainer struct {
|
type SolaceContainer struct {
|
||||||
testcontainers.Container
|
testcontainers.Container
|
||||||
AmqpConnectionString string
|
AmqpConnectionString string
|
||||||
sempClient *Client
|
sempClient SempClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSolaceContainer starts a container and
|
// NewSolaceContainer starts a container and
|
||||||
|
|
@ -79,23 +175,19 @@ func NewSolaceContainer(ctx context.Context) (*SolaceContainer, error) {
|
||||||
sempApiBaseUrl := fmt.Sprintf("http://%s:%s/SEMP/v2", host, sempPort.Port())
|
sempApiBaseUrl := fmt.Sprintf("http://%s:%s/SEMP/v2", host, sempPort.Port())
|
||||||
|
|
||||||
// Construct SEMP client
|
// Construct SEMP client
|
||||||
sempClient := NewClient(
|
sempClient := SempClient{client: http.Client{}, sempApiBaseUrl: sempApiBaseUrl, username: "admin", password: "admin"}
|
||||||
sempApiBaseUrl,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
BasicAuth("admin", "admin"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Poll queue endpoint until solace is ready to interact
|
// Poll queue endpoint until solace is ready to interact
|
||||||
solaceStarting := true
|
solaceStarting := true
|
||||||
for solaceStarting {
|
for solaceStarting {
|
||||||
_, err := sempClient.RequestWithoutBody(
|
err := sempClient.RequestWithoutBody(
|
||||||
ctx,
|
ctx,
|
||||||
"GET",
|
"GET",
|
||||||
"/config/msgVpns/default/queues/test",
|
"/config/msgVpns/default/queues/test",
|
||||||
)
|
)
|
||||||
if err != nil && strings.Contains(err.Error(), "NOT_FOUND") {
|
if err != nil && strings.Contains(err.Error(), "NOT_FOUND") {
|
||||||
solaceStarting = false
|
solaceStarting = false
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
} else {
|
} else {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +214,7 @@ func (c SolaceContainer) QueueCreate(ctx context.Context, queueName string) erro
|
||||||
queueConfig["maxBindCount"] = 100
|
queueConfig["maxBindCount"] = 100
|
||||||
|
|
||||||
// Create the queue
|
// Create the queue
|
||||||
_, err := c.sempClient.RequestWithBody(
|
err := c.sempClient.RequestWithBody(
|
||||||
ctx,
|
ctx,
|
||||||
"POST",
|
"POST",
|
||||||
"/config/msgVpns/default/queues",
|
"/config/msgVpns/default/queues",
|
||||||
|
|
@ -135,7 +227,7 @@ func (c SolaceContainer) QueueCreate(ctx context.Context, queueName string) erro
|
||||||
func (c SolaceContainer) QueueExists(ctx context.Context, queueName string) (bool, error) {
|
func (c SolaceContainer) QueueExists(ctx context.Context, queueName string) (bool, error) {
|
||||||
|
|
||||||
// Check if exists
|
// Check if exists
|
||||||
_, err := c.sempClient.RequestWithoutBody(
|
err := c.sempClient.RequestWithoutBody(
|
||||||
ctx,
|
ctx,
|
||||||
"GET",
|
"GET",
|
||||||
fmt.Sprintf("/config/msgVpns/default/queues/%s", queueName),
|
fmt.Sprintf("/config/msgVpns/default/queues/%s", queueName),
|
||||||
|
|
@ -165,7 +257,7 @@ func (c SolaceContainer) QueueDeleteIfExists(ctx context.Context, queueName stri
|
||||||
|
|
||||||
// Delete if exists
|
// Delete if exists
|
||||||
if exists {
|
if exists {
|
||||||
_, err := c.sempClient.RequestWithoutBody(
|
err := c.sempClient.RequestWithoutBody(
|
||||||
ctx,
|
ctx,
|
||||||
"DELETE",
|
"DELETE",
|
||||||
fmt.Sprintf("/config/msgVpns/default/queues/%s", queueName),
|
fmt.Sprintf("/config/msgVpns/default/queues/%s", queueName),
|
||||||
|
|
@ -191,7 +283,7 @@ func (c SolaceContainer) TopicSubscriptionCreate(ctx context.Context, queueName
|
||||||
subscriptionConfig["subscriptionTopic"] = topicName
|
subscriptionConfig["subscriptionTopic"] = topicName
|
||||||
|
|
||||||
// Create the subscription
|
// Create the subscription
|
||||||
_, err := c.sempClient.RequestWithBody(ctx, "POST", url, subscriptionConfig)
|
err := c.sempClient.RequestWithBody(ctx, "POST", url, subscriptionConfig)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -7,7 +7,6 @@ require (
|
||||||
github.com/Azure/go-amqp v1.1.0
|
github.com/Azure/go-amqp v1.1.0
|
||||||
github.com/bufbuild/protovalidate-go v0.6.4
|
github.com/bufbuild/protovalidate-go v0.6.4
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/testcontainers/testcontainers-go v0.33.0
|
github.com/testcontainers/testcontainers-go v0.33.0
|
||||||
go.opentelemetry.io/otel v1.24.0
|
go.opentelemetry.io/otel v1.24.0
|
||||||
|
|
@ -36,7 +35,6 @@ require (
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/cel-go v0.21.0 // indirect
|
github.com/google/cel-go v0.21.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
|
|
||||||
12
go.sum
12
go.sum
|
|
@ -39,8 +39,6 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
|
|
@ -64,12 +62,6 @@ 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
|
||||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
|
||||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
|
|
@ -82,10 +74,6 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue