Skip to content

Commit

Permalink
improve tests and move pull secret stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkowl committed Jul 4, 2023
1 parent 572b34d commit 15691e8
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 108 deletions.
47 changes: 4 additions & 43 deletions pkg/containerinstall/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ package containerinstall
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"

"github.com/Azure/go-autorest/autorest/to"
Expand All @@ -24,7 +21,6 @@ import (
"github.com/opencontainers/runtime-spec/specs-go"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/util/steps"
)

Expand All @@ -44,18 +40,14 @@ var (
}
)

type auths struct {
Auths map[string]map[string]interface{} `json:"auths,omitempty"`
}

func (m *manager) Install(ctx context.Context, sub *api.SubscriptionDocument, doc *api.OpenShiftClusterDocument, version *api.OpenShiftVersion) error {
s := []steps.Step{
steps.Action(func(context.Context) error {
options := &images.PullOptions{
Quiet: to.BoolPtr(true),
Policy: to.StringPtr("always"),
Username: to.StringPtr(m.pullSecret[0]),
Password: to.StringPtr(m.pullSecret[1]),
Username: to.StringPtr(m.pullSecret.Username),
Password: to.StringPtr(m.pullSecret.Password),
}

_, err := images.Pull(m.conn, version.Properties.InstallerPullspec, options)
Expand Down Expand Up @@ -109,8 +101,8 @@ func (m *manager) startContainer(ctx context.Context, version *api.OpenShiftVers
"OPENSHIFT_INSTALL_RELEASE_IMAGE_OVERRIDE": version.Properties.OpenShiftPullspec,
}

for _, i := range devEnvVars {
s.Env["ARO_"+i] = os.Getenv(i)
for _, envvar := range devEnvVars {
s.Env["ARO_"+envvar] = os.Getenv(envvar)
}

s.Mounts = append(s.Mounts, specs.Mount{
Expand Down Expand Up @@ -222,34 +214,3 @@ func (m *manager) cleanupContainers(ctx context.Context) error {
}
return nil
}

func pullSecretFromEnv(_env env.Interface) (out [2]string, err error) {
pullSecrets := &auths{}
err = json.Unmarshal([]byte(os.Getenv("PULL_SECRET")), pullSecrets)
if err != nil {
return
}

auth, ok := pullSecrets.Auths[_env.ACRDomain()]
if !ok {
return out, fmt.Errorf("missing %s key in PULL_SECRET", _env.ACRDomain())
}

token, ok := auth["auth"]
if !ok {
return out, errors.New("maformed auth token")
}

decoded, err := base64.StdEncoding.DecodeString(token.(string))
if err != nil {
return out, err
}

split := strings.Split(string(decoded), ":")
if len(split) != 2 {
return out, fmt.Errorf("not username:pass in %s config", _env.ACRDomain())
}

out = [2]string{split[0], split[1]}
return out, nil
}
140 changes: 77 additions & 63 deletions pkg/containerinstall/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,80 +7,94 @@ import (
"context"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/Azure/go-autorest/autorest/to"
"github.com/containers/podman/v4/pkg/bindings/containers"
"github.com/containers/podman/v4/pkg/bindings/images"
"github.com/containers/podman/v4/pkg/specgen"
"github.com/onsi/gomega"
"github.com/onsi/gomega/types"
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"

"github.com/Azure/ARO-RP/pkg/util/uuid"
testlog "github.com/Azure/ARO-RP/test/util/log"
)

const TEST_PULLSPEC = "registry.access.redhat.com/ubi8/go-toolset:1.18.4"

func TestPodman(t *testing.T) {
ctx := context.Background()
conn, err := getConnection(ctx)
if err != nil {
t.Skipf("unable to access podman: %v", err)
}

hook, log := testlog.New()
randomName := uuid.DefaultGenerator.Generate()

s := specgen.NewSpecGenerator(TEST_PULLSPEC, false)
s.Name = randomName
s.Entrypoint = []string{"/bin/bash", "-c", "echo 'hello'"}

_, err = images.Pull(conn, TEST_PULLSPEC, &images.PullOptions{Policy: to.StringPtr("missing")})
if err != nil {
t.Fatal(err)
}

id, err := runContainer(conn, log, s)
if err != nil {
t.Fatal(err)
}

exit, err := containers.Wait(conn, id, nil)
if err != nil {
t.Error(err)
}
if exit != 0 {
t.Errorf("exit code was %d, not 0", exit)
}

err = getContainerLogs(conn, log, id)
if err != nil {
t.Error(err)
}

_, err = containers.Remove(conn, id, &containers.RemoveOptions{Force: to.BoolPtr(true)})
if err != nil {
t.Error(err)
}

entries := []map[string]types.GomegaMatcher{

{
"msg": gomega.Equal("created container " + randomName + " with ID " + id),
"level": gomega.Equal(logrus.InfoLevel),
},
{
"msg": gomega.Equal("started container " + id),
"level": gomega.Equal(logrus.InfoLevel),
},
{
"msg": gomega.Equal("stdout: hello\n"),
"level": gomega.Equal(logrus.InfoLevel),
},
}

err = testlog.AssertLoggingOutput(hook, entries)
if err != nil {
t.Fatal(err)
}
var _ = Describe("Podman", Ordered, func() {
var err error
var conn context.Context
var hook *test.Hook
var log *logrus.Entry
var containerName string
var containerID string

BeforeAll(func(ctx context.Context) {
var err error
conn, err = getConnection(ctx)
if err != nil {
Skip("unable to access podman: %v")
}

hook, log = testlog.New()
containerName = uuid.DefaultGenerator.Generate()
})

It("can pull images", func() {
_, err = images.Pull(conn, TEST_PULLSPEC, &images.PullOptions{Policy: to.StringPtr("missing")})
Expect(err).To(BeNil())
})

It("can start a container", func() {
s := specgen.NewSpecGenerator(TEST_PULLSPEC, false)
s.Name = containerName
s.Entrypoint = []string{"/bin/bash", "-c", "echo 'hello'"}

containerID, err = runContainer(conn, log, s)
Expect(err).To(BeNil())
})

It("can wait for completion", func() {
exit, err := containers.Wait(conn, containerID, nil)
Expect(err).To(BeNil())
Expect(exit).To(Equal(0), "exit code was %d, not 0", exit)
})

It("can fetch container logs", func() {
err = getContainerLogs(conn, log, containerID)
Expect(err).To(BeNil())

entries := []map[string]types.GomegaMatcher{
{
"msg": Equal("created container " + containerName + " with ID " + containerID),
"level": Equal(logrus.InfoLevel),
},
{
"msg": Equal("started container " + containerID),
"level": Equal(logrus.InfoLevel),
},
{
"msg": Equal("stdout: hello\n"),
"level": Equal(logrus.InfoLevel),
},
}

err = testlog.AssertLoggingOutput(hook, entries)
Expect(err).To(BeNil())
})

AfterAll(func() {
if containerID != "" {
_, err = containers.Remove(conn, containerID, &containers.RemoveOptions{Force: to.BoolPtr(true)})
Expect(err).To(BeNil())
}
})
})

func TestContainerInstall(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "ContainerInstall Suite")
}
6 changes: 4 additions & 2 deletions pkg/containerinstall/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ package containerinstall
import (
"context"
"errors"
"os"

"github.com/sirupsen/logrus"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/util/pullsecret"
)

type ContainerInstaller interface {
Expand All @@ -23,7 +25,7 @@ type manager struct {
env env.Interface

clusterUUID string
pullSecret [2]string
pullSecret *pullsecret.UserPass

success bool
}
Expand All @@ -34,7 +36,7 @@ func New(ctx context.Context, log *logrus.Entry, env env.Interface, clusterUUID
return nil, errors.New("running cluster installs in a container is only run in development")
}

pullSecret, err := pullSecretFromEnv(env)
pullSecret, err := pullsecret.Extract(os.Getenv("PULL_SECRET"), env.ACRDomain())
if err != nil {
return nil, err
}
Expand Down
56 changes: 56 additions & 0 deletions pkg/util/pullsecret/extract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package pullsecret

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
)

type UserPass struct {
Username string
Password string
}

func userPassFromBase64(secret string) (*UserPass, error) {
decoded, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return nil, errors.New("malformed auth token")
}

split := strings.Split(string(decoded), ":")
if len(split) != 2 {
return nil, errors.New("auth token not in format of username:password")
}

return &UserPass{
Username: split[0],
Password: split[1],
}, nil
}

// Extract decodes a username and password for a given domain from a
// JSON-encoded pull secret (e.g. from docker auth)
func Extract(rawPullSecret, domain string) (*UserPass, error) {
pullSecrets := &pullSecret{}
err := json.Unmarshal([]byte(rawPullSecret), pullSecrets)
if err != nil {
return nil, errors.New("malformed pullsecret (invalid JSON)")
}

auth, ok := pullSecrets.Auths[domain]
if !ok {
return nil, fmt.Errorf("missing '%s' key in pullsecret", domain)
}

token, ok := auth["auth"]
if !ok {
return nil, errors.New("malformed pullsecret (no auth key)")
}

return userPassFromBase64(token.(string))
}
59 changes: 59 additions & 0 deletions pkg/util/pullsecret/extract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package pullsecret

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Extract()", func() {
It("correctly decodes a pullsecret", func() {
pullSecret := "{\"auths\": {\"example.com\": {\"auth\": \"dGVzdHVzZXI6dGVzdHBhc3M=\"}}}"

correctlyExtracted, err := Extract(pullSecret, "example.com")
Expect(err).To(BeNil())
Expect(correctlyExtracted).To(Equal(&UserPass{Username: "testuser", Password: "testpass"}))
})

It("errors if no pullsecret for that name exists", func() {
pullSecret := "{\"auths\": {\"example.com\": {\"auth\": \"dGVzdHVzZXI6dGVzdHBhc3M=\"}}}"

_, err := Extract(pullSecret, "missingexample.com")
Expect(err).To(MatchError("missing 'missingexample.com' key in pullsecret"))
})

It("errors if the json is invalid", func() {
_, err := Extract("\"", "example.com")
Expect(err).To(MatchError("malformed pullsecret (invalid JSON)"))
})

It("errors if the base64 is invalid", func() {
pullSecret := "{\"auths\": {\"example.com\": {\"auth\": \"5\"}}}"

_, err := Extract(pullSecret, "example.com")
Expect(err).To(MatchError("malformed auth token"))
})

It("errors if the base64 does not contain a username and password", func() {
pullSecret := "{\"auths\": {\"example.com\": {\"auth\": \"c29tZXRoaW5nZWxzZQ==\"}}}"

_, err := Extract(pullSecret, "example.com")
Expect(err).To(MatchError("auth token not in format of username:password"))
})

It("errors if pullsecret has no auth key for domain", func() {
pullSecret := "{\"auths\": {\"example.com\": {\"password\": \"dGVzdHVzZXI6dGVzdHBhc3M=\"}}}"

_, err := Extract(pullSecret, "example.com")
Expect(err).To(MatchError("malformed pullsecret (no auth key)"))
})
})

func TestPullSecret(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "PullSecret Suite")
}

0 comments on commit 15691e8

Please sign in to comment.