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

Fix external registry issues #1230

Merged
merged 14 commits into from
Jan 30, 2023
8 changes: 2 additions & 6 deletions src/cmd/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package cmd

import (
"net/url"
"os"

"github.com/anchore/syft/cmd/syft/cli"
Expand Down Expand Up @@ -234,16 +233,13 @@ func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command {
return err
}
tunnelReg.Connect(cluster.ZarfRegistry, false)
registryURL, err := url.Parse(tunnelReg.HTTPEndpoint())
if err != nil {
return err
}

// Add the correct authentication to the crane command options
authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PullUsername, zarfState.RegistryInfo.PullPassword)
*cranePlatformOptions = append(*cranePlatformOptions, authOption)
registryEndpoint := tunnelReg.Endpoint()

return originalCatalogFn(cmd, []string{registryURL.Host})
return originalCatalogFn(cmd, []string{registryEndpoint})
}

return craneCatalog
Expand Down
11 changes: 0 additions & 11 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const (
ZarfSBOMDir = "zarf-sbom"
ZarfPackagePrefix = "zarf-package-"

ZarfInClusterContainerRegistryURL = "http://zarf-registry-http.zarf.svc.cluster.local:5000"
ZarfInClusterContainerRegistryNodePort = 31999

ZarfInClusterGitServiceURL = "http://zarf-gitea-http.zarf.svc.cluster.local:3000"
Expand Down Expand Up @@ -165,16 +164,6 @@ func GetValidPackageExtensions() [3]string {
return [...]string{".tar.zst", ".tar", ".zip"}
}

// GetRegistry returns the registry URL based on the Zarf state.
func GetRegistry(state types.ZarfState) string {
// If a node port is populated, then we are using a registry internal to the cluster. Ignore the provided address and use localhost
if state.RegistryInfo.NodePort >= 30000 {
return fmt.Sprintf("%s:%d", IPV4Localhost, state.RegistryInfo.NodePort)
}

return state.RegistryInfo.Address
}

// GetAbsCachePath gets the absolute cache path for images and git repos.
func GetAbsCachePath() string {
homePath, _ := os.UserHomeDir()
Expand Down
3 changes: 1 addition & 2 deletions src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,5 @@ const (

// Collection of reusable error messages.
var (
ErrInitNotFound = errors.New("this command requires a zarf-init package, but one was not found on the local system. Re-run the last command again without '--confirm' to download the package")
ErrNotAServiceURL = errors.New("the provided URL does not match service url format of http://{SERVICE_NAME}.{NAMESPACE}.svc.cluster.local:{PORT}")
ErrInitNotFound = errors.New("this command requires a zarf-init package, but one was not found on the local system. Re-run the last command again without '--confirm' to download the package")
)
2 changes: 1 addition & 1 deletion src/internal/agent/hooks/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) {
if err != nil {
return nil, fmt.Errorf(lang.AgentErrGetState, err)
}
containerRegistryURL := config.GetRegistry(zarfState)
containerRegistryURL := zarfState.RegistryInfo.Address

// update the image host for each init container
for idx, container := range pod.Spec.InitContainers {
Expand Down
2 changes: 1 addition & 1 deletion src/internal/agent/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func StartWebhook() {
}
}()

message.Info(lang.AgentErrStart)
message.Infof(lang.AgentInfoPort, httpPort)

// listen shutdown signal
signalChan := make(chan os.Signal, 1)
Expand Down
2 changes: 1 addition & 1 deletion src/internal/cluster/injector.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (c *Cluster) injectorIsReady(spinner *message.Spinner) bool {

spinner.Updatef("Testing the injector for seed image availability")

seedRegistry := fmt.Sprintf("http://%s/v2/library/%s/manifests/%s", tunnel.Endpoint(), config.ZarfSeedImage, config.ZarfSeedTag)
seedRegistry := fmt.Sprintf("%s/v2/library/%s/manifests/%s", tunnel.HTTPEndpoint(), config.ZarfSeedImage, config.ZarfSeedTag)
if resp, err := http.Get(seedRegistry); err != nil || resp.StatusCode != 200 {
// Just debug log the output because failures just result in trying the next image
message.Debug(resp, err)
Expand Down
3 changes: 1 addition & 2 deletions src/internal/cluster/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

corev1 "k8s.io/api/core/v1"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/pkg/message"
)

Expand Down Expand Up @@ -48,7 +47,7 @@ func (c *Cluster) GenerateRegistryPullCreds(namespace, name string) (*corev1.Sec
fieldValue := zarfState.RegistryInfo.PullUsername + ":" + credential
authEncodedValue := base64.StdEncoding.EncodeToString([]byte(fieldValue))

registry := config.GetRegistry(zarfState)
registry := zarfState.RegistryInfo.Address
// Create the expected structure for the dockerconfigjson
dockerConfigJSON := DockerConfig{
Auths: DockerConfigEntry{
Expand Down
8 changes: 6 additions & 2 deletions src/internal/cluster/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,15 @@ func (c *Cluster) PostSeedRegistry(tempPath types.TempPaths) error {
}

func (c *Cluster) fillInEmptyContainerRegistryValues(containerRegistry types.RegistryInfo) types.RegistryInfo {
// Set default NodePort if none was provided
if containerRegistry.NodePort == 0 {
containerRegistry.NodePort = config.ZarfInClusterContainerRegistryNodePort
}

// Set default url if an external registry was not provided
if containerRegistry.Address == "" {
containerRegistry.InternalRegistry = true
containerRegistry.NodePort = config.ZarfInClusterContainerRegistryNodePort
containerRegistry.Address = fmt.Sprintf("http://%s:%d", config.IPV4Localhost, containerRegistry.NodePort)
containerRegistry.Address = fmt.Sprintf("%s:%d", config.IPV4Localhost, containerRegistry.NodePort)
}

// Generate a push-user password if not provided by init flag
Expand Down
87 changes: 66 additions & 21 deletions src/internal/cluster/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"syscall"
"time"

"github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/types"

"github.com/defenseunicorns/zarf/src/config"
Expand Down Expand Up @@ -67,6 +66,13 @@ type Tunnel struct {
spinner *message.Spinner
}

// ServiceInfo contains information necessary for connecting to a cluster service.
type ServiceInfo struct {
Namespace string
Name string
Port int
}

// PrintConnectTable will print a table of all Zarf connect matches found in the cluster.
func (c *Cluster) PrintConnectTable() error {
list, err := c.Kube.GetServicesByLabelExists(v1.NamespaceAll, config.ZarfConnectLabelName)
Expand All @@ -91,33 +97,72 @@ func (c *Cluster) PrintConnectTable() error {
return nil
}

// IsServiceURL will check if the provided string is a valid serviceURL based on if it properly matches a validating regexp.
func IsServiceURL(serviceURL string) bool {
parsedURL, err := url.Parse(serviceURL)
// ServiceInfoFromNodePortURL takes a nodePortURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format:
// Example nodePortURL: 127.0.0.1:{PORT}.
func ServiceInfoFromNodePortURL(nodePortURL string) *ServiceInfo {
// Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes)
parsedURL, err := url.Parse(nodePortURL)
if err != nil {
return false
parsedURL, err = url.Parse("scheme://" + nodePortURL)
if err != nil {
return nil
}
}

// Match hostname against local cluster service format
pattern := regexp.MustCompile(serviceURLPattern)
matches := pattern.FindStringSubmatch(parsedURL.Hostname())
// Match hostname against localhost ip/hostnames
hostname := parsedURL.Hostname()
if hostname != config.IPV4Localhost && hostname != "localhost" {
return nil
}

// Get the node port from the nodeportURL.
nodePort, err := strconv.Atoi(parsedURL.Port())
if err != nil {
return nil
}
if nodePort < 30000 || nodePort > 32767 {
return nil
}

kube, err := k8s.NewWithWait(message.Debugf, labels, defaultTimeout)
if err != nil {
return nil
}

// If incomplete match, return an error
return len(matches) == 3
services, err := kube.GetServices("")
if err != nil {
return nil
}

for _, svc := range services.Items {
if svc.Spec.Type == "NodePort" {
for _, port := range svc.Spec.Ports {
if int(port.NodePort) == nodePort {
return &ServiceInfo{
Namespace: svc.Namespace,
Name: svc.Name,
Port: int(port.Port),
}
}
}
}
}

return nil
}

// NewTunnelFromServiceURL takes a serviceURL and parses it to create a tunnel to the cluster. The string is expected to follow the following format:
// ServiceInfoFromServiceURL takes a serviceURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format:
// Example serviceURL: http://{SERVICE_NAME}.{NAMESPACE}.svc.cluster.local:{PORT}.
func NewTunnelFromServiceURL(serviceURL string) (*Tunnel, error) {
func ServiceInfoFromServiceURL(serviceURL string) *ServiceInfo {
parsedURL, err := url.Parse(serviceURL)
if err != nil {
return nil, err
return nil
}

// Get the remote port from the serviceURL.
remotePort, err := strconv.Atoi(parsedURL.Port())
if err != nil {
return nil, err
return nil
}

// Match hostname against local cluster service format.
Expand All @@ -126,14 +171,14 @@ func NewTunnelFromServiceURL(serviceURL string) (*Tunnel, error) {

// If incomplete match, return an error.
if len(matches) != 3 {
return nil, lang.ErrNotAServiceURL
return nil
}

// Use the matched values to create a new tunnel.
name := matches[pattern.SubexpIndex("name")]
namespace := matches[pattern.SubexpIndex("namespace")]

return NewTunnel(namespace, SvcResource, name, 0, remotePort)
return &ServiceInfo{
Namespace: matches[pattern.SubexpIndex("namespace")],
Name: matches[pattern.SubexpIndex("name")],
Port: remotePort,
}
}

// NewTunnel will create a new Tunnel struct.
Expand Down Expand Up @@ -261,7 +306,7 @@ func (tunnel *Tunnel) Connect(target string, blocking bool) error {
return nil
}

// Endpoint returns the tunnel endpoint.
// Endpoint returns the tunnel ip address and port (i.e. for docker registries)
func (tunnel *Tunnel) Endpoint() string {
message.Debug("tunnel.Endpoint()")
return fmt.Sprintf("127.0.0.1:%d", tunnel.localPort)
Expand Down
10 changes: 5 additions & 5 deletions src/internal/packager/git/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ func (g *Git) CreateReadOnlyUser() error {
tunnel.Connect(cluster.ZarfGit, false)
defer tunnel.Close()

tunnelURL := tunnel.Endpoint()
tunnelURL := tunnel.HTTPEndpoint()

// Determine if the read only user already exists
getUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users", tunnelURL)
getUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users", tunnelURL)
getUserRequest, _ := netHttp.NewRequest("GET", getUserEndpoint, nil)
out, err := g.DoHTTPThings(getUserRequest, g.Server.PushUsername, g.Server.PushPassword)
message.Debugf("GET %s:\n%s", getUserEndpoint, string(out))
Expand Down Expand Up @@ -60,7 +60,7 @@ func (g *Git) CreateReadOnlyUser() error {
"password": g.Server.PullPassword,
}
updateUserData, _ := json.Marshal(updateUserBody)
updateUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername)
updateUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername)
updateUserRequest, _ := netHttp.NewRequest("PATCH", updateUserEndpoint, bytes.NewBuffer(updateUserData))
out, err = g.DoHTTPThings(updateUserRequest, g.Server.PushUsername, g.Server.PushPassword)
message.Debugf("PATCH %s:\n%s", updateUserEndpoint, string(out))
Expand All @@ -80,7 +80,7 @@ func (g *Git) CreateReadOnlyUser() error {
}

// Send API request to create the user
createUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users", tunnelURL)
createUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users", tunnelURL)
createUserRequest, _ := netHttp.NewRequest("POST", createUserEndpoint, bytes.NewBuffer(createUserData))
out, err = g.DoHTTPThings(createUserRequest, g.Server.PushUsername, g.Server.PushPassword)
message.Debugf("POST %s:\n%s", createUserEndpoint, string(out))
Expand All @@ -95,7 +95,7 @@ func (g *Git) CreateReadOnlyUser() error {
"allow_create_organization": false,
}
updateUserData, _ := json.Marshal(updateUserBody)
updateUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername)
updateUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername)
updateUserRequest, _ := netHttp.NewRequest("PATCH", updateUserEndpoint, bytes.NewBuffer(updateUserData))
out, err = g.DoHTTPThings(updateUserRequest, g.Server.PushUsername, g.Server.PushPassword)
message.Debugf("PATCH %s:\n%s", updateUserEndpoint, string(out))
Expand Down
19 changes: 12 additions & 7 deletions src/internal/packager/images/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,28 @@ func (i *ImgConfig) PushToZarfRegistry() error {
return err
}
target = cluster.ZarfRegistry
} else if cluster.IsServiceURL(i.RegInfo.Address) {
// If this is a serviceURL, create a port-forward tunnel to that resource
if tunnel, err = cluster.NewTunnelFromServiceURL(i.RegInfo.Address); err != nil {
return err
} else {
svcInfo := cluster.ServiceInfoFromNodePortURL(i.RegInfo.Address)
if svcInfo != nil {
// If this is a service, create a port-forward tunnel to that resource
if tunnel, err = cluster.NewTunnel(svcInfo.Namespace, cluster.SvcResource, svcInfo.Name, 0, svcInfo.Port); err != nil {
return err
}
}
}

if tunnel != nil {
tunnel.Connect(target, false)
defer tunnel.Close()
registryURL = tunnel.Endpoint()
} else {
registryURL = i.RegInfo.Address
}

spinner := message.NewProgressSpinner("Storing images in the zarf registry")
defer spinner.Stop()

pushOptions := config.GetCraneAuthOption(i.RegInfo.PushUsername, i.RegInfo.PushPassword)
pushOptions := []crane.Option{config.GetCraneAuthOption(i.RegInfo.PushUsername, i.RegInfo.PushPassword)}
message.Debugf("crane pushOptions = %#v", pushOptions)

for _, src := range i.ImgList {
Expand All @@ -65,7 +70,7 @@ func (i *ImgConfig) PushToZarfRegistry() error {

message.Debugf("crane.Push() %s:%s -> %s)", i.TarballPath, src, offlineNameCRC)

if err = crane.Push(img, offlineNameCRC, pushOptions); err != nil {
if err = crane.Push(img, offlineNameCRC, pushOptions...); err != nil {
return err
}
}
Expand All @@ -79,7 +84,7 @@ func (i *ImgConfig) PushToZarfRegistry() error {

message.Debugf("crane.Push() %s:%s -> %s)", i.TarballPath, src, offlineName)

if err = crane.Push(img, offlineName, pushOptions); err != nil {
if err = crane.Push(img, offlineName, pushOptions...); err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/internal/packager/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func Generate(cfg *types.PackagerConfig) (Values, error) {

generated.htpasswd = fmt.Sprintf("%s\\n%s", pushUser, pullUser)

generated.registry = config.GetRegistry(cfg.State)
generated.registry = regInfo.Address

return generated, nil
}
Expand Down
5 changes: 5 additions & 0 deletions src/pkg/k8s/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ func (k *K8s) GetService(namespace, serviceName string) (*corev1.Service, error)
return k.Clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{})
}

// GetServices returns a list of services in the provided namespace. To search all namespaces, pass "" in the namespace arg.
func (k *K8s) GetServices(namespace string) (*corev1.ServiceList, error) {
return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{})
}

// GetServicesByLabel returns a list of matched services given a label and value. To search all namespaces, pass "" in the namespace arg.
func (k *K8s) GetServicesByLabel(namespace, label, value string) (*corev1.ServiceList, error) {
// Creat the selector and add the requirement
Expand Down
9 changes: 5 additions & 4 deletions src/pkg/packager/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,17 +401,18 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error
tryPush := func() error {
gitClient := git.New(p.cfg.State.GitServer)

// If this is a serviceURL, create a port-forward tunnel to that resource
if cluster.IsServiceURL(gitClient.Server.Address) {
tunnel, err := cluster.NewTunnelFromServiceURL(gitClient.Server.Address)
svcInfo := cluster.ServiceInfoFromServiceURL(gitClient.Server.Address)
// If this is a service, create a port-forward tunnel to that resource
if svcInfo != nil {
tunnel, err := cluster.NewTunnel(svcInfo.Namespace, cluster.SvcResource, svcInfo.Name, 0, svcInfo.Port)

if err != nil {
return err
}

tunnel.Connect("", false)
defer tunnel.Close()
gitClient.Server.Address = fmt.Sprintf("http://%s", tunnel.Endpoint())
gitClient.Server.Address = tunnel.HTTPEndpoint()
}

// Convert the repo URL to a Zarf-formatted repo name
Expand Down
Loading