Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ostree commit resolver for otk: otk-resolve-ostree-commit (COMPOSER-2349) #935

Merged
merged 5 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/otk-resolve-ostree-commit/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

var (
Run = run
)
86 changes: 86 additions & 0 deletions cmd/otk-resolve-ostree-commit/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"encoding/json"
"fmt"
"io"
"os"

"github.com/osbuild/images/pkg/ostree"
)

// All otk external inputs are nested under a top-level "tree"
type Tree struct {
Tree Input `json:"tree"`
}

// Input represents the user-provided inputs that will be used to resolve an
mvo5 marked this conversation as resolved.
Show resolved Hide resolved
// ostree commit ID.
type Input struct {
// URL of the repo where the commit can be fetched.
URL string `json:"url"`

// Ref to resolve.
Ref string `json:"ref"`

// Whether to use RHSM secrets when resolving and fetching the commit.
RHSM bool `json:"rhsm,omitempty"`
}

// Output contains everything needed to write a manifest that requires pulling
// an ostree commit.
type Output struct {
Const OutputConst `json:"const"`
}

type OutputConst struct {
// Ref of the commit (can be empty).
Ref string `json:"ref,omitempty"`

// URL of the repo where the commit can be fetched.
URL string `json:"url"`

// Secrets type to use when pulling the ostree commit content
// (e.g. org.osbuild.rhsm.consumer).
Secrets string `json:"secrets,omitempty"`

// Checksum of the commit.
Checksum string `json:"checksum"`
}

func run(r io.Reader, w io.Writer) error {
var inputTree Tree
if err := json.NewDecoder(r).Decode(&inputTree); err != nil {
return err
}

sourceSpec := ostree.SourceSpec(inputTree.Tree)
commitSpec, err := ostree.Resolve(sourceSpec)
if err != nil {
return fmt.Errorf("failed to resolve ostree commit: %w", err)
}

output := map[string]Output{
"tree": {
Const: OutputConst{
Ref: commitSpec.Ref,
URL: commitSpec.URL,
Secrets: commitSpec.Secrets,
Checksum: commitSpec.Checksum,
},
},
}
outputJson, err := json.MarshalIndent(output, "", " ")
if err != nil {
return fmt.Errorf("cannot marshal response: %w", err)
}
fmt.Fprintf(w, "%s\n", outputJson)
return nil
}

func main() {
if err := run(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err.Error())
os.Exit(1)
}
}
193 changes: 193 additions & 0 deletions cmd/otk-resolve-ostree-commit/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package main_test

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

resolver "github.com/osbuild/images/cmd/otk-resolve-ostree-commit"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var commitMap = map[string]string{
"centos/9/x86_64/edge": "d04105393ca0617856b34f897842833d785522e41617e17dca2063bf97e294ef",
"fake/ref/f": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"fake/ref/9": "9999999999999999999999999999999999999999999999999999999999999999",
"test/ref/alpha": "9b1ea9a8e10dc27d4ea40545bec028ad8e360dd26d18de64b0f6217833a8443d",
"test/ref/one": "7433e1b49fb136d61dcca49ebe34e713fdbb8e29bf328fe90819628f71b86105",
}

// Create a test server that responds with the commit ID that corresponds to
// the ref.
func createTestServer(refIDs map[string]string) *httptest.Server {
handler := http.NewServeMux()
handler.HandleFunc("/refs/heads/", func(w http.ResponseWriter, r *http.Request) {
reqRef := strings.TrimPrefix(r.URL.Path, "/refs/heads/")
id, ok := refIDs[reqRef]
if !ok {
http.NotFound(w, r)
return
}
fmt.Fprint(w, id)
supakeen marked this conversation as resolved.
Show resolved Hide resolved
})

return httptest.NewServer(handler)
}

func TestResolver(t *testing.T) {
require := require.New(t)
mvo5 marked this conversation as resolved.
Show resolved Hide resolved
assert := assert.New(t)

repoServer := createTestServer(commitMap)
defer repoServer.Close()

url := repoServer.URL
for ref, id := range commitMap {
inputReq, err := json.Marshal(map[string]map[string]string{
"tree": {
"url": url,
"ref": ref,
},
})
require.NoError(err)

inpBuf := bytes.NewBuffer(inputReq)
outBuf := &bytes.Buffer{}

assert.NoError(resolver.Run(inpBuf, outBuf))

var output map[string]map[string]map[string]string
require.NoError(json.Unmarshal(outBuf.Bytes(), &output))

expOutput := map[string]map[string]map[string]string{
"tree": {
"const": {
"url": url,
"ref": ref,
"checksum": id,
},
},
}
assert.Equal(expOutput, output)
}
}

func TestResolverByID(t *testing.T) {
require := require.New(t)
assert := assert.New(t)

for _, id := range commitMap {
inputReq, err := json.Marshal(map[string]map[string]string{
"tree": {
"ref": id,
},
})
require.NoError(err)

inpBuf := bytes.NewBuffer(inputReq)
outBuf := &bytes.Buffer{}

assert.NoError(resolver.Run(inpBuf, outBuf))

var output map[string]map[string]map[string]string
require.NoError(json.Unmarshal(outBuf.Bytes(), &output))

expOutput := map[string]map[string]map[string]string{
"tree": {
"const": {
"ref": id,
"checksum": id,
"url": "",
},
},
}
assert.Equal(expOutput, output)
}
}
func TestResolverIDwithURL(t *testing.T) {

require := require.New(t)
assert := assert.New(t)

// the URL is not used when the ref is a commit ID, but it should be returned in the output
url := "https://doesnt-matter.example.org"
for _, id := range commitMap {
inputReq, err := json.Marshal(map[string]map[string]string{
"tree": {
"ref": id,
"url": url,
},
})
require.NoError(err)

inpBuf := bytes.NewBuffer(inputReq)
outBuf := &bytes.Buffer{}

assert.NoError(resolver.Run(inpBuf, outBuf))

var output map[string]map[string]map[string]string
require.NoError(json.Unmarshal(outBuf.Bytes(), &output))

expOutput := map[string]map[string]map[string]string{
"tree": {
"const": {
"ref": id,
"checksum": id,
"url": url,
},
},
}
assert.Equal(expOutput, output)
}
}

func TestResolverErrors(t *testing.T) {

repoServer := createTestServer(commitMap)
defer repoServer.Close()

type testCase struct {
url string
ref string
errSubstring string
}

testCases := map[string]testCase{
"bad-ref": {
url: "doesn't matter",
ref: "---",
errSubstring: "Invalid ostree ref or commit",
},
"ref-not-found": {
url: repoServer.URL,
ref: "good/ref/but/does-not-exist",
errSubstring: "returned status: 404 Not Found",
},
}

for name := range testCases {
tc := testCases[name]
t.Run(name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)

inputReq, err := json.Marshal(map[string]map[string]string{
"tree": {
"url": tc.url,
"ref": tc.ref,
},
})
require.NoError(err)

inpBuf := bytes.NewBuffer(inputReq)
outBuf := &bytes.Buffer{}

assert.ErrorContains(resolver.Run(inpBuf, outBuf), tc.errSubstring)
})
}
}
5 changes: 4 additions & 1 deletion pkg/ostree/ostree.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
if consumerCerts {
if subs == nil {
subs, err = rhsm.LoadSystemSubscriptions()
if subs.Consumer == nil || err != nil {
if err != nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
}
if subs.Consumer == nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
mvo5 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down
Loading