Skip to content

Commit

Permalink
Revert "Make it possible to sync from ACR to ACR"
Browse files Browse the repository at this point in the history
janboll authored Dec 11, 2024
1 parent f3b2601 commit 7f7ba15
Showing 3 changed files with 28 additions and 198 deletions.
11 changes: 5 additions & 6 deletions tooling/image-sync/example.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
repositories:
- registry.k8s.io/external-dns/external-dns
- testingone.azurecr.io/azure-cli
numberOfTags: 2
acrTargetRegistry: testingtrgt.azurecr.io
- quay.io/acm-d/rhtap-hypershift-operator
- quay.io/app-sre/uhc-clusters-service
numberOfTags: 40
quaySecretfile: /home/jboll/workspace/opensource/quay-secret.json
acrRegistry: testing.azurecr.io
tenantId: 64dc69e4-d083-49fc-9569-ebece1dd1408
secrets:
- registry: testingone.azurecr.io
azureSecretfile: /home/jboll/workspace/opensource/other-secret.json
115 changes: 3 additions & 112 deletions tooling/image-sync/internal/repository.go
Original file line number Diff line number Diff line change
@@ -163,13 +163,13 @@ func NewAzureContainerRegistry(cfg *SyncConfig) *AzureContainerRegistry {
}
}

client, err := azcontainerregistry.NewClient(fmt.Sprintf("https://%s", cfg.AcrTargetRegistry), cred, nil)
client, err := azcontainerregistry.NewClient(fmt.Sprintf("https://%s", cfg.AcrRegistry), cred, nil)
if err != nil {
Log().Fatalf("failed to create client: %v", err)
}

return &AzureContainerRegistry{
acrName: cfg.AcrTargetRegistry,
acrName: cfg.AcrRegistry,
acrClient: client,
credential: cred,
httpClient: &http.Client{Timeout: time.Duration(cfg.RequestTimeout) * time.Second},
@@ -295,124 +295,18 @@ func (a *AzureContainerRegistry) GetTags(ctx context.Context, repository string)
return tags, nil
}

type ACRWithTokenAuth struct {
httpclient *http.Client
acrName string
numberOftags int
bearerToken string
}

type AccessSecret struct {
AccessToken string `json:"access_token"`
}

type rawACRTagResponse struct {
Tags []rawACRTags
}

type rawACRTags struct {
Name string
}

func getACRBearerToken(ctx context.Context, secret AzureSecretFile, acrName string) (string, error) {
scope := "repository:*:*"
path := fmt.Sprintf("https://%s/oauth2/token?service=%s&scope=%s", acrName, acrName, scope)

Log().Debugw("Creating request", "path", path)
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", secret.BasicAuthEncoded()))
if err != nil {
return "", fmt.Errorf("failed to create request: %v", err)
}

// todo replace with timeout enabled client
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("failed to send request: %v", err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("unexpected status code %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %v", err)
}

var accessSecret AccessSecret

err = json.Unmarshal(body, &accessSecret)
if err != nil {
return "", fmt.Errorf("failed to unmarshal response: %v", err)
}

return accessSecret.AccessToken, nil
}

func NewACRWithTokenAuth(cfg *SyncConfig, acrName string, bearerToken string) *ACRWithTokenAuth {
return &ACRWithTokenAuth{
httpclient: &http.Client{Timeout: time.Duration(cfg.RequestTimeout) * time.Second},
acrName: acrName,
bearerToken: bearerToken,
numberOftags: cfg.NumberOfTags,
}
}

func (n *ACRWithTokenAuth) GetTags(ctx context.Context, image string) ([]string, error) {
Log().Debugw("Getting tags for image", "image", image)

path := fmt.Sprintf("https://%s/acr/v1/%s/_tags?orderby=%s&n=%d", n.acrName, image, azcontainerregistry.ArtifactTagOrderByLastUpdatedOnDescending, n.numberOftags)
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.bearerToken))
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}

Log().Debugw("Sending request", "path", path)
resp, err := n.httpclient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %v", err)
}
Log().Debugw("Got response", "statuscode", resp.StatusCode)

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %v", err)
}

var acrResponse rawACRTagResponse
err = json.Unmarshal(body, &acrResponse)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
}

tagList := make([]string, 0)

for _, tag := range acrResponse.Tags {
tagList = append(tagList, tag.Name)
}

return tagList, nil
}

// OCIRegistry implements OCI Repository access
type OCIRegistry struct {
httpclient *http.Client
baseURL string
numberOftags int
bearerToken string
}

// NewOCIRegistry creates a new OCIRegistry access client
func NewOCIRegistry(cfg *SyncConfig, baseURL, bearerToken string) *OCIRegistry {
func NewOCIRegistry(cfg *SyncConfig, baseURL string) *OCIRegistry {
o := &OCIRegistry{
httpclient: &http.Client{Timeout: time.Duration(cfg.RequestTimeout) * time.Second},
numberOftags: cfg.NumberOfTags,
bearerToken: bearerToken,
}
if !strings.HasPrefix(o.baseURL, "https://") {
o.baseURL = fmt.Sprintf("https://%s", baseURL)
@@ -467,9 +361,6 @@ func (o *OCIRegistry) GetTags(ctx context.Context, image string) ([]string, erro

path := fmt.Sprintf("%s/v2/%s/tags/list", o.baseURL, image)
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if o.bearerToken != "" {
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", o.bearerToken))
}
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
100 changes: 20 additions & 80 deletions tooling/image-sync/internal/sync.go
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ package internal

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
@@ -23,34 +22,19 @@ func Log() *zap.SugaredLogger {
type SyncConfig struct {
Repositories []string
NumberOfTags int
Secrets []Secrets
AcrTargetRegistry string
QuaySecretFile string
AcrRegistry string
TenantId string
RequestTimeout int
AddLatest bool
ManagedIdentityClientID string
}
type Secrets struct {
Registry string
SecretFile string
AzureSecretfile string
}

// BearerSecret is the secret for the source OCI registry
type BearerSecret struct {
// QuaySecret is the secret for quay.io
type QuaySecret struct {
BearerToken string
}

// AzureSecret is the token configured in the ACR
type AzureSecretFile struct {
Username string
Password string
}

func (a AzureSecretFile) BasicAuthEncoded() string {
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", a.Username, a.Password)))
}

// Copy copies an image from one registry to another
func Copy(ctx context.Context, dstreference, srcreference string, dstauth, srcauth *types.DockerAuthConfig) error {
policyctx, err := signature.NewPolicyContext(&signature.Policy{
@@ -84,26 +68,12 @@ func Copy(ctx context.Context, dstreference, srcreference string, dstauth, srcau
return err
}

func readBearerSecret(filename string) (*BearerSecret, error) {
secretBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var secret BearerSecret
err = json.Unmarshal(secretBytes, &secret)
if err != nil {
return nil, err
}

return &secret, nil
}

func readAzureSecret(filename string) (*AzureSecretFile, error) {
func readQuaySecret(filename string) (*QuaySecret, error) {
secretBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var secret AzureSecretFile
var secret QuaySecret
err = json.Unmarshal(secretBytes, &secret)
if err != nil {
return nil, err
@@ -133,48 +103,19 @@ func DoSync(cfg *SyncConfig) error {
Log().Infow("Syncing images", "images", cfg.Repositories, "numberoftags", cfg.NumberOfTags)
ctx := context.Background()

srcRegistries := make(map[string]Registry)
var err error

for _, secret := range cfg.Secrets {
if secret.Registry == "quay.io" {
quaySecret, err := readBearerSecret(secret.SecretFile)
if err != nil {
return fmt.Errorf("error reading secret file: %w %s", err, secret.SecretFile)
}
qr := NewQuayRegistry(cfg, quaySecret.BearerToken)
srcRegistries[secret.Registry] = qr
} else {
if strings.HasSuffix(secret.Registry, "azurecr.io") ||
strings.HasSuffix(secret.Registry, "azurecr.cn") ||
strings.HasSuffix(secret.Registry, "azurecr.us") {
azureSecret, err := readAzureSecret(secret.AzureSecretfile)
if err != nil {
return fmt.Errorf("error reading azure secret file: %w %s", err, secret.AzureSecretfile)
}
bearerSecret, err := getACRBearerToken(ctx, *azureSecret, secret.Registry)
if err != nil {
return fmt.Errorf("error getting ACR bearer token: %w", err)
}
srcRegistries[secret.Registry] = NewACRWithTokenAuth(cfg, secret.Registry, bearerSecret)
} else {
s, err := readBearerSecret(secret.SecretFile)
bearerSecret := s.BearerToken
if err != nil {
return fmt.Errorf("error reading secret file: %w %s", err, secret.SecretFile)
}
srcRegistries[secret.Registry] = NewOCIRegistry(cfg, secret.Registry, bearerSecret)
}
}
quaySecret, err := readQuaySecret(cfg.QuaySecretFile)
if err != nil {
return fmt.Errorf("error reading secret file: %w %s", err, cfg.QuaySecretFile)
}
qr := NewQuayRegistry(cfg, quaySecret.BearerToken)

targetACR := NewAzureContainerRegistry(cfg)
acrPullSecret, err := targetACR.GetPullSecret(ctx)
acr := NewAzureContainerRegistry(cfg)
acrPullSecret, err := acr.GetPullSecret(ctx)
if err != nil {
return fmt.Errorf("error getting pull secret: %w", err)
}

targetACRAuth := types.DockerAuthConfig{Username: "00000000-0000-0000-0000-000000000000", Password: acrPullSecret.RefreshToken}
acrAuth := types.DockerAuthConfig{Username: "00000000-0000-0000-0000-000000000000", Password: acrPullSecret.RefreshToken}

for _, repoName := range cfg.Repositories {
var srcTags, acrTags []string
@@ -184,29 +125,28 @@ func DoSync(cfg *SyncConfig) error {

Log().Infow("Syncing repository", "repository", repoName, "baseurl", baseURL)

if client, ok := srcRegistries[baseURL]; ok {
srcTags, err = client.GetTags(ctx, repoName)
if baseURL == "quay.io" {
srcTags, err = qr.GetTags(ctx, repoName)
if err != nil {
return fmt.Errorf("error getting quay tags: %w", err)
}
Log().Debugw("Got tags from quay", "tags", srcTags)
} else {
// No secret defined, create a default client without auth
oci := NewOCIRegistry(cfg, baseURL, "")
oci := NewOCIRegistry(cfg, baseURL)
srcTags, err = oci.GetTags(ctx, repoName)
if err != nil {
return fmt.Errorf("error getting oci tags: %w", err)
}
Log().Debugw(fmt.Sprintf("Got tags from %s", baseURL), "repo", repoName, "tags", srcTags)
}

exists, err := targetACR.RepositoryExists(ctx, repoName)
exists, err := acr.RepositoryExists(ctx, repoName)
if err != nil {
return fmt.Errorf("error getting ACR repository information: %w", err)
}

if exists {
acrTags, err = targetACR.GetTags(ctx, repoName)
acrTags, err = acr.GetTags(ctx, repoName)
if err != nil {
return fmt.Errorf("error getting ACR tags: %w", err)
}
@@ -221,10 +161,10 @@ func DoSync(cfg *SyncConfig) error {

for _, tagToSync := range tagsToSync {
source := fmt.Sprintf("%s/%s:%s", baseURL, repoName, tagToSync)
target := fmt.Sprintf("%s/%s:%s", cfg.AcrTargetRegistry, repoName, tagToSync)
target := fmt.Sprintf("%s/%s:%s", cfg.AcrRegistry, repoName, tagToSync)
Log().Infow("Copying images", "images", tagToSync, "from", source, "to", target)

err = Copy(ctx, target, source, &targetACRAuth, nil)
err = Copy(ctx, target, source, &acrAuth, nil)
if err != nil {
return fmt.Errorf("error copying image: %w", err)
}

0 comments on commit 7f7ba15

Please sign in to comment.