feat: snap can be queried by revision and ``track/risk/branch`` (#4439)

---------
Signed-off-by: Yuntao Hu <victorhu493@gmail.com>
This commit is contained in:
VictorHuu 2025-12-19 04:41:36 +08:00 committed by GitHub
parent 74c9380248
commit c9760d2341
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 523 additions and 21 deletions

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -31,15 +32,21 @@ type remoteSnap struct {
URL string URL string
} }
const NotSpecifiedRevision int = 0
type snapIdentity struct { type snapIdentity struct {
Name string Name string
Channel string Channel string
Architecture string Architecture string
Revision int
} }
func (s snapIdentity) String() string { func (s snapIdentity) String() string {
parts := []string{s.Name} parts := []string{s.Name}
// revision will supersede channel
if s.Revision != NotSpecifiedRevision {
parts = append(parts, fmt.Sprintf(":%d", s.Revision))
} else {
if s.Channel != "" { if s.Channel != "" {
parts = append(parts, fmt.Sprintf("@%s", s.Channel)) parts = append(parts, fmt.Sprintf("@%s", s.Channel))
} }
@ -47,6 +54,7 @@ func (s snapIdentity) String() string {
if s.Architecture != "" { if s.Architecture != "" {
parts = append(parts, fmt.Sprintf(" (%s)", s.Architecture)) parts = append(parts, fmt.Sprintf(" (%s)", s.Architecture))
} }
}
return strings.Join(parts, "") return strings.Join(parts, "")
} }
@ -166,17 +174,21 @@ func getSnapFileInfo(ctx context.Context, fs afero.Fs, path string, hashes []cry
// The request can be: // The request can be:
// - A snap name (e.g., "etcd") // - A snap name (e.g., "etcd")
// - A snap name with channel (e.g., "etcd@beta" or "etcd@2.3/stable") // - A snap name with channel (e.g., "etcd@beta" or "etcd@2.3/stable")
// - A snap name with revision (e.g. etcd:249@stable)
func resolveRemoteSnap(request, architecture string) (*remoteSnap, error) { func resolveRemoteSnap(request, architecture string) (*remoteSnap, error) {
if architecture == "" { if architecture == "" {
architecture = defaultArchitecture architecture = defaultArchitecture
} }
snapName, channel := parseSnapRequest(request) snapName, revision, channel, err := parseSnapRequest(request)
if err != nil {
return nil, err
}
id := snapIdentity{ id := snapIdentity{
Name: snapName, Name: snapName,
Channel: channel, Channel: channel,
Architecture: architecture, Architecture: architecture,
Revision: revision,
} }
client := newSnapcraftClient() client := newSnapcraftClient()
@ -194,15 +206,26 @@ func resolveRemoteSnap(request, architecture string) (*remoteSnap, error) {
}, nil }, nil
} }
// parseSnapRequest parses a snap request into name and channel // parseSnapRequest parses a snap request into name and revision/channel
// Examples: // Examples:
// - "etcd" -> name="etcd", channel="stable" (default) // - "etcd" -> name="etcd", channel="stable" (default)
// - "etcd@beta" -> name="etcd", channel="beta" // - "etcd@beta" -> name="etcd", channel="beta"
// - "etcd@2.3/stable" -> name="etcd", channel="2.3/stable" // - "etcd@2.3/stable" -> name="etcd", channel="2.3/stable"
func parseSnapRequest(request string) (name, channel string) { // - "etcd:249@2.3/stable" -> name="etcd" revision=249 (channel not working because revision has been assigned)
func parseSnapRequest(request string) (name string, revision int, channel string, err error) {
parts := strings.SplitN(request, "@", 2) parts := strings.SplitN(request, "@", 2)
name = parts[0] name = parts[0]
divisions := strings.Split(parts[0], ":")
// handle revision first
if len(divisions) == 2 {
name = divisions[0]
revision, err = strconv.Atoi(divisions[1])
if err != nil {
return "", NotSpecifiedRevision, "", err
}
return name, revision, "", err
}
if len(parts) == 2 { if len(parts) == 2 {
channel = parts[1] channel = parts[1]
} }
@ -210,8 +233,7 @@ func parseSnapRequest(request string) (name, channel string) {
if channel == "" { if channel == "" {
channel = defaultChannel channel = defaultChannel
} }
return name, NotSpecifiedRevision, channel, err
return name, channel
} }
func downloadSnap(getter intFile.Getter, info *remoteSnap, dest string) error { func downloadSnap(getter intFile.Getter, info *remoteSnap, dest string) error {

View File

@ -511,75 +511,106 @@ func TestParseSnapRequest(t *testing.T) {
name string name string
request string request string
expectedName string expectedName string
expectedRevision int
expectedChannel string expectedChannel string
expectedError require.ErrorAssertionFunc
}{ }{
{ {
name: "snap name only - uses default channel", name: "snap name only - uses default channel",
request: "etcd", request: "etcd",
expectedName: "etcd", expectedName: "etcd",
expectedChannel: "stable", expectedChannel: "stable",
expectedError: require.NoError,
}, },
{ {
name: "snap with beta channel", name: "snap with beta channel",
request: "etcd@beta", request: "etcd@beta",
expectedName: "etcd", expectedName: "etcd",
expectedChannel: "beta", expectedChannel: "beta",
expectedError: require.NoError,
}, },
{ {
name: "snap with edge channel", name: "snap with edge channel",
request: "etcd@edge", request: "etcd@edge",
expectedName: "etcd", expectedName: "etcd",
expectedChannel: "edge", expectedChannel: "edge",
expectedError: require.NoError,
}, },
{ {
name: "snap with version track", name: "snap with version track",
request: "etcd@2.3/stable", request: "etcd@2.3/stable",
expectedName: "etcd", expectedName: "etcd",
expectedChannel: "2.3/stable", expectedChannel: "2.3/stable",
expectedError: require.NoError,
}, },
{ {
name: "snap with complex channel path", name: "snap with complex channel path",
request: "mysql@8.0/candidate", request: "mysql@8.0/candidate",
expectedName: "mysql", expectedName: "mysql",
expectedChannel: "8.0/candidate", expectedChannel: "8.0/candidate",
expectedError: require.NoError,
}, },
{ {
name: "snap with multiple @ symbols - only first is delimiter", name: "snap with multiple @ symbols - only first is delimiter",
request: "app@beta@test", request: "app@beta@test",
expectedName: "app", expectedName: "app",
expectedChannel: "beta@test", expectedChannel: "beta@test",
expectedError: require.NoError,
},
{
name: "snap with revision",
request: "etcd:249",
expectedName: "etcd",
expectedRevision: 249,
expectedError: require.NoError,
},
{
name: "snap with revision so the channel doesn't work",
request: "etcd:249@2.3/beta",
expectedName: "etcd",
expectedRevision: 249,
expectedError: require.NoError,
}, },
{ {
name: "empty snap name with channel", name: "empty snap name with channel",
request: "@stable", request: "@stable",
expectedName: "", expectedName: "",
expectedChannel: "stable", expectedChannel: "stable",
expectedError: require.NoError,
}, },
{ {
name: "snap name with empty channel - uses default", name: "snap name with empty channel - uses default",
request: "etcd@", request: "etcd@",
expectedName: "etcd", expectedName: "etcd",
expectedChannel: "stable", expectedChannel: "stable",
expectedError: require.NoError,
}, },
{ {
name: "hyphenated snap name", name: "hyphenated snap name",
request: "hello-world@stable", request: "hello-world@stable",
expectedName: "hello-world", expectedName: "hello-world",
expectedChannel: "stable", expectedChannel: "stable",
expectedError: require.NoError,
}, },
{ {
name: "snap name with numbers", name: "snap name with numbers",
request: "app123", request: "app123",
expectedName: "app123", expectedName: "app123",
expectedChannel: "stable", expectedChannel: "stable",
expectedError: require.NoError,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
name, channel := parseSnapRequest(tt.request) name, revision, channel, err := parseSnapRequest(tt.request)
assert.Equal(t, tt.expectedName, name) assert.Equal(t, tt.expectedName, name)
if tt.expectedRevision != NotSpecifiedRevision {
assert.Equal(t, tt.expectedRevision, revision)
} else {
assert.Equal(t, tt.expectedChannel, channel) assert.Equal(t, tt.expectedChannel, channel)
}
require.NoError(t, err)
}) })
} }
} }

View File

@ -5,6 +5,9 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"regexp"
"strconv"
"strings"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
) )
@ -58,17 +61,133 @@ type snapFindResponse struct {
} `json:"results"` } `json:"results"`
} }
type SnapRisk string
const (
RiskStable SnapRisk = "stable"
RiskCandidate SnapRisk = "candidate"
RiskBeta SnapRisk = "beta"
RiskEdge SnapRisk = "edge"
RiskUnknown SnapRisk = "unknown"
)
func isValidSnapRisk(r SnapRisk) bool {
switch r {
case RiskStable, RiskCandidate, RiskBeta, RiskEdge:
return true
default:
return false
}
}
func stringToSnapRisk(s string) SnapRisk {
r := SnapRisk(s)
if !isValidSnapRisk(r) {
return RiskUnknown
}
return r
}
func getRevisionFromURL(cm snapChannelMapEntry) (rev int, err error) {
re := regexp.MustCompile(`(\d+)\.snap$`)
match := re.FindStringSubmatch(cm.Download.URL)
if len(match) < 2 {
err = fmt.Errorf("could not determine revision from %s", cm.Download.URL)
return
}
rev, err = strconv.Atoi(match[1])
return
}
// isEligibleChannel determines whether a candidate channel satisfies a requested
// channel. Both channels are parsed into {track, risk} pairs.
//
// Matching rules:
// - If the request includes a track, both track and risk must match exactly.
// - If the request omits the track (e.g., "stable"), any candidate track is
// accepted as long as the risk matches.
//
// Examples:
//
// candidate="3.2/stable", request="stable" -> true
// candidate="3.2/stable", request="3.2/stable" -> true
// candidate="3.2/stable", request="3.2/beta" -> false
// candidate="3.2/beta", request="stable" -> false
// candidate="3.2/alpha", request="alpha" -> false(alpha is an invalid risk level)
// candidate="3.2/stable/fix-for-bug123", request="stable" -> true
// candidate="3.2/stable/fix-for-bug123", request="3.2/stable" -> true
func isEligibleChannel(candidate, request string) (bool, error) {
cTrack, cRisk, cBranch := splitChannel(candidate)
rTrack, rRisk, rBranch := splitChannel(request)
if rTrack == "" && rRisk == "" && rBranch == "" {
return false, fmt.Errorf("there is no such risk in the channel(only stable/candidate/beta/edge are valid)")
}
if rTrack != "" {
return cTrack == rTrack && cRisk == rRisk && (cBranch == rBranch || rBranch == ""), nil
}
return cRisk == rRisk && (cBranch == rBranch || rBranch == ""), nil
}
func splitChannel(ch string) (track string, risk string, branch string) {
parts := strings.SplitN(ch, "/", 3)
if stringToSnapRisk(parts[0]) != RiskUnknown {
if len(parts) == 1 {
return "", parts[0], "" // no track
} else if len(parts) == 2 {
return "", parts[0], parts[1]
}
} else if len(parts) >= 2 && stringToSnapRisk(parts[1]) != RiskUnknown {
if len(parts) == 3 {
return parts[0], parts[1], parts[2]
} else if len(parts) == 2 {
return parts[0], parts[1], ""
}
}
return "", "", ""
}
func matchSnapDownloadURL(cm snapChannelMapEntry, id snapIdentity) (string, error) {
// revision will supersede channel
if id.Revision != NotSpecifiedRevision {
rev, err2 := getRevisionFromURL(cm)
if err2 == nil && rev == id.Revision {
return cm.Download.URL, nil
}
} else if cm.Channel.Architecture == id.Architecture {
matched, err2 := isEligibleChannel(cm.Channel.Name, id.Channel)
if err2 != nil {
return "", err2
}
if matched {
return cm.Download.URL, nil
}
}
return "", nil
}
// GetSnapDownloadURL retrieves the download URL for a snap package // GetSnapDownloadURL retrieves the download URL for a snap package
func (c *snapcraftClient) GetSnapDownloadURL(id snapIdentity) (string, error) { func (c *snapcraftClient) GetSnapDownloadURL(id snapIdentity) (string, error) {
apiURL := c.InfoAPIURL + id.Name apiURL := c.InfoAPIURL + id.Name
if id.Revision == NotSpecifiedRevision {
log.WithFields("name", id.Name, "channel", id.Channel, "architecture", id.Architecture).Trace("requesting snap info") log.WithFields("name", id.Name, "channel", id.Channel, "architecture", id.Architecture).Trace("requesting snap info")
} else {
log.WithFields("name", id.Name, "revision", id.Revision, "architecture", id.Architecture).Trace("requesting snap info")
}
req, err := http.NewRequest(http.MethodGet, apiURL, nil) req, err := http.NewRequest(http.MethodGet, apiURL, nil)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to create HTTP request: %w", err) return "", fmt.Errorf("failed to create HTTP request: %w", err)
} }
if id.Revision != NotSpecifiedRevision {
q := req.URL.Query()
q.Add("revision", fmt.Sprintf("%d", id.Revision))
req.URL.RawQuery = q.Encode()
}
req.Header.Set("Snap-Device-Series", defaultSeries) req.Header.Set("Snap-Device-Series", defaultSeries)
resp, err := c.HTTPClient.Do(req) resp, err := c.HTTPClient.Do(req)
@ -107,9 +226,11 @@ func (c *snapcraftClient) GetSnapDownloadURL(id snapIdentity) (string, error) {
} }
for _, cm := range info.ChannelMap { for _, cm := range info.ChannelMap {
if cm.Channel.Architecture == id.Architecture && cm.Channel.Name == id.Channel { url, err2 := matchSnapDownloadURL(cm, id)
return cm.Download.URL, nil if url == "" && err2 == nil {
continue
} }
return url, err2
} }
return "", fmt.Errorf("no matching snap found for %s", id.String()) return "", fmt.Errorf("no matching snap found for %s", id.String())

View File

@ -161,6 +161,126 @@ func TestSnapcraftClient_GetSnapDownloadURL(t *testing.T) {
expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap", expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
expectError: require.NoError, expectError: require.NoError,
}, },
{
name: "successful download URL retrieval (w/ track)",
snapID: snapIdentity{
Name: "etcd",
Channel: "stable",
Architecture: "amd64",
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "3.2/stable",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
},
},
},
},
expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
expectError: require.NoError,
},
{
name: "successful download URL retrieval (w/ track&branch)",
snapID: snapIdentity{
Name: "etcd",
Channel: "stable",
Architecture: "amd64",
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "3.2/stable/fix-for-bug123",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
},
},
},
},
expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
expectError: require.NoError,
},
{
name: "branch unmatched",
snapID: snapIdentity{
Name: "etcd",
Channel: "stable/fix-for-bug124",
Architecture: "amd64",
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "3.2/stable/fix-for-bug123",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
},
},
},
},
expectError: require.Error,
errorContains: "no matching snap found",
},
{
name: "risk unmatched",
snapID: snapIdentity{
Name: "etcd",
Channel: "stable",
Architecture: "amd64",
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "latest/beta",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
},
},
},
},
expectError: require.Error,
errorContains: "no matching snap found",
},
{
name: "illegal risk",
snapID: snapIdentity{
Name: "etcd",
Channel: "foobar",
Architecture: "amd64",
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "latest/beta",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap",
},
},
},
},
expectError: require.Error,
errorContains: "there is no such risk",
},
{ {
name: "region-locked snap - exists but unavailable", name: "region-locked snap - exists but unavailable",
snapID: snapIdentity{ snapID: snapIdentity{
@ -351,6 +471,214 @@ func TestSnapcraftClient_GetSnapDownloadURL(t *testing.T) {
} }
} }
func TestSnapcraftClient_GetSnapDownloadURL_WithVersion(t *testing.T) {
tests := []struct {
name string
snapID snapIdentity
infoResponse snapcraftInfo
infoStatusCode int
findResponse *snapFindResponse
findStatusCode int
expectedURL string
expectError require.ErrorAssertionFunc
errorContains string
}{
{
name: "successful download URL retrieval",
snapID: snapIdentity{
Name: "etcd",
Channel: "stable",
Architecture: "amd64",
Revision: 249,
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "stable",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/TKebVGcPeDKoOqAmNmczU2oWLtsojKD5_249.snap",
},
},
},
},
expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/TKebVGcPeDKoOqAmNmczU2oWLtsojKD5_249.snap",
expectError: require.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.expectError == nil {
tt.expectError = require.NoError
}
infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series"))
expectedPath := "/" + tt.snapID.Name
assert.Equal(t, expectedPath, r.URL.Path)
w.WriteHeader(tt.infoStatusCode)
if tt.infoStatusCode == http.StatusOK {
responseBytes, err := json.Marshal(tt.infoResponse)
require.NoError(t, err)
w.Write(responseBytes)
}
}))
defer infoServer.Close()
var findServer *httptest.Server
if tt.findResponse != nil || tt.findStatusCode != 0 {
findServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series"))
assert.Equal(t, tt.snapID.Name, r.URL.Query().Get("name-startswith"))
statusCode := tt.findStatusCode
if statusCode == 0 {
statusCode = http.StatusOK
}
w.WriteHeader(statusCode)
if tt.findResponse != nil && statusCode == http.StatusOK {
responseBytes, err := json.Marshal(tt.findResponse)
require.NoError(t, err)
w.Write(responseBytes)
}
}))
defer findServer.Close()
}
client := &snapcraftClient{
InfoAPIURL: infoServer.URL + "/",
HTTPClient: &http.Client{},
}
if findServer != nil {
client.FindAPIURL = findServer.URL
}
url, err := client.GetSnapDownloadURL(tt.snapID)
tt.expectError(t, err)
if err != nil {
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
return
}
assert.Equal(t, tt.expectedURL, url)
})
}
}
func TestSnapcraftClient_GetSnapDownloadURL_DoesntExist(t *testing.T) {
tests := []struct {
name string
snapID snapIdentity
infoResponse snapcraftInfo
infoStatusCode int
findResponse *snapFindResponse
findStatusCode int
expectedURL string
expectError require.ErrorAssertionFunc
errorContains string
}{
{
name: "non-existent snap with revision",
snapID: snapIdentity{
Name: "etcd",
Channel: "stable",
Architecture: "amd64",
Revision: 248,
},
infoStatusCode: http.StatusOK,
infoResponse: snapcraftInfo{
ChannelMap: []snapChannelMapEntry{
{
Channel: snapChannel{
Architecture: "amd64",
Name: "stable",
},
Download: snapDownload{
URL: "https://api.snapcraft.io/api/v1/snaps/download/TKebVGcPeDKoOqAmNmczU2oWLtsojKD5_249.snap",
},
},
},
},
expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/TKebVGcPeDKoOqAmNmczU2oWLtsojKD5_249.snap",
expectError: func(t require.TestingT, err error, msgAndArgs ...interface{}) {
require.EqualError(t, err, "no matching snap found for etcd:248")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.expectError == nil {
tt.expectError = require.NoError
}
infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series"))
expectedPath := "/" + tt.snapID.Name
assert.Equal(t, expectedPath, r.URL.Path)
w.WriteHeader(tt.infoStatusCode)
if tt.infoStatusCode == http.StatusOK {
responseBytes, err := json.Marshal(tt.infoResponse)
require.NoError(t, err)
w.Write(responseBytes)
}
}))
defer infoServer.Close()
var findServer *httptest.Server
if tt.findResponse != nil || tt.findStatusCode != 0 {
findServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series"))
assert.Equal(t, tt.snapID.Name, r.URL.Query().Get("name-startswith"))
statusCode := tt.findStatusCode
if statusCode == 0 {
statusCode = http.StatusOK
}
w.WriteHeader(statusCode)
if tt.findResponse != nil && statusCode == http.StatusOK {
responseBytes, err := json.Marshal(tt.findResponse)
require.NoError(t, err)
w.Write(responseBytes)
}
}))
defer findServer.Close()
}
client := &snapcraftClient{
InfoAPIURL: infoServer.URL + "/",
HTTPClient: &http.Client{},
}
if findServer != nil {
client.FindAPIURL = findServer.URL
}
url, err := client.GetSnapDownloadURL(tt.snapID)
tt.expectError(t, err)
if err != nil {
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
return
}
assert.Equal(t, tt.expectedURL, url)
})
}
}
func TestSnapcraftClient_GetSnapDownloadURL_InvalidJSON(t *testing.T) { func TestSnapcraftClient_GetSnapDownloadURL_InvalidJSON(t *testing.T) {
infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)