-
Notifications
You must be signed in to change notification settings - Fork 1
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
Use and modify docker-mirror to mirror repositories to public ECR registries #9
Changes from all commits
328378b
ef971ab
eadec92
9b759ea
73aa59f
1e4b7d5
75e3289
a4e9c1b
9ce4d5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
## The source repositories and target repositories are one-to-one correspondence. | ||
sourceRepos: | ||
- registry: opentelemetry | ||
name: opentelemetry-operator | ||
host: quay.io | ||
- registry: kubebuilder | ||
name: kube-rbac-proxy | ||
host: gcr.io | ||
|
||
targetRepos: | ||
- registry: public.ecr.aws/aws-observability | ||
name: adot-operator | ||
- registry: public.ecr.aws/aws-observability | ||
name: mirror-kube-rbac-proxy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
docker "github.com/fsouza/go-dockerclient" | ||
) | ||
|
||
func getDockerCredentials(registry string) (*docker.AuthConfiguration, error) { | ||
authOptions, err := docker.NewAuthConfigurationsFromDockerCfg() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
creds, ok := authOptions.Configs[registry] | ||
if !ok { | ||
return nil, fmt.Errorf("no auth found for %s", registry) | ||
} | ||
|
||
return &creds, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
|
||
"github.com/aws/aws-sdk-go-v2/service/ecrpublic" | ||
"github.com/cenkalti/backoff" | ||
) | ||
|
||
const ( | ||
operatorRepo = "adot-operator" | ||
rbacProxyRepo = "mirror-kube-rbac-proxy" | ||
) | ||
|
||
type ecrManager struct { | ||
client *ecrpublic.Client | ||
repositories map[string]bool | ||
} | ||
|
||
func (e *ecrManager) exists(name string) bool { | ||
if _, ok := e.repositories[name]; ok { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
func (e *ecrManager) ensure(name string) error { | ||
if e.exists(name) { | ||
return nil | ||
} | ||
|
||
return e.create(name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're creating Production ECR Repo. Do you think we can add some name edit check to make sure we can arbitrary create some test repo name in Prod ECR namespace? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I'm not sure if I understand this correctly. By name edit check, do you mean that we need to check if the registry name is as expected (e.g. |
||
} | ||
|
||
func (e *ecrManager) create(name string) error { | ||
if name != operatorRepo && name != rbacProxyRepo { | ||
return fmt.Errorf("wrong repository name: %s", name) | ||
} | ||
|
||
_, err := e.client.CreateRepository(context.TODO(), &ecrpublic.CreateRepositoryInput{ | ||
RepositoryName: &name, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
e.repositories[name] = true | ||
return nil | ||
} | ||
|
||
func (e *ecrManager) buildCache(nextToken *string) error { | ||
if nextToken == nil { | ||
log.Println("Loading the list of ECR repositories") | ||
} | ||
|
||
resp, err := e.client.DescribeRepositories(context.TODO(), &ecrpublic.DescribeRepositoriesInput{ | ||
NextToken: nextToken, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if e.repositories == nil { | ||
e.repositories = make(map[string]bool) | ||
} | ||
|
||
for _, repo := range resp.Repositories { | ||
e.repositories[*repo.RepositoryName] = true | ||
} | ||
|
||
// keep paging as long as there is a token for the next page | ||
if resp.NextToken != nil { | ||
err := e.buildCache(resp.NextToken) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// no next token means we hit the last page | ||
if nextToken == nil { | ||
log.Println("Done loading ECR repositories") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (e *ecrManager) buildCacheBackoff() backoff.Operation { | ||
return func() error { | ||
return e.buildCache(nil) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"log" | ||
"runtime" | ||
"sync" | ||
"time" | ||
|
||
awsconfig "github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/aws/aws-sdk-go-v2/service/ecrpublic" | ||
"github.com/cenkalti/backoff" | ||
docker "github.com/fsouza/go-dockerclient" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
const ecrPublicRegion = "us-east-1" | ||
|
||
var ( | ||
config Config | ||
backoffSettings *backoff.ExponentialBackOff | ||
) | ||
|
||
type Config struct { | ||
SourceRepos []Repository `yaml:"sourceRepos"` | ||
TargetRepos []Repository `yaml:"targetRepos"` | ||
} | ||
|
||
type Repository struct { | ||
Registry string `yaml:"registry"` | ||
Name string `yaml:"name"` | ||
Host string `yaml:"host"` | ||
} | ||
|
||
// Create the Docker client which will be used to pull, retag and push images. | ||
func createDockerClient() (*docker.Client, error) { | ||
client, err := docker.NewClientFromEnv() | ||
return client, err | ||
} | ||
|
||
// worker is the mirror working function. | ||
func worker(wg *sync.WaitGroup, workerCh chan []Repository, dc *DockerClient, ecrm *ecrManager) { | ||
for repos := range workerCh { | ||
log.Printf("Starting to mirror repo %s/%s/%s to repo %s/%s", repos[0].Host, repos[0].Registry, repos[0].Name, repos[1].Registry, repos[1].Name) | ||
|
||
m := mirror{ | ||
dockerClient: dc, | ||
ecrManager: ecrm, | ||
} | ||
if err := m.setup(repos); err != nil { | ||
log.Printf("Failed to setup mirror for repo %s/%s/%s: %v", repos[0].Host, repos[0].Registry, repos[0].Name, err) | ||
wg.Done() | ||
continue | ||
} | ||
|
||
m.work() | ||
log.Printf("From repo %s/%s/%s to repo %s/%s mirror completed", repos[0].Host, repos[0].Registry, repos[0].Name, repos[1].Registry, repos[1].Name) | ||
wg.Done() | ||
} | ||
} | ||
|
||
func main() { | ||
content, err := ioutil.ReadFile("tools/release/adot-operator-images-mirror/config.yaml") | ||
if err != nil { | ||
log.Fatalf("Could not read config file: %v", err) | ||
} | ||
|
||
if err = yaml.Unmarshal(content, &config); err != nil { | ||
log.Fatalf("Could not parse config file: %v", err) | ||
} | ||
|
||
log.Println("Creating Docker client") | ||
var client DockerClient | ||
client, err = createDockerClient() | ||
if err != nil { | ||
log.Fatalf("Could not create Docker client: %v", err) | ||
} | ||
|
||
info, err := client.Info() | ||
if err != nil { | ||
log.Fatalf("Could not get Docker info: %v", err) | ||
} | ||
log.Printf("Connected to Docker daemon: %s @ %s", info.Name, info.ServerVersion) | ||
|
||
log.Println("Creating AWS client") | ||
cfg, err := awsconfig.LoadDefaultConfig(context.TODO()) | ||
if err != nil { | ||
log.Fatalf("Unable to load AWS SDK config: %v", err) | ||
} | ||
// Override the AWS region with the ecrPublicRegion for ECR authentication. | ||
cfg.Region = ecrPublicRegion | ||
|
||
ecrManager := &ecrManager{client: ecrpublic.NewFromConfig(cfg)} | ||
backoffSettings = backoff.NewExponentialBackOff() | ||
backoffSettings.InitialInterval = 1 * time.Second | ||
backoffSettings.MaxElapsedTime = 10 * time.Second | ||
notifyError := func(err error, d time.Duration) { | ||
log.Fatalf("%v (%s)", err, d.String()) | ||
} | ||
|
||
// Build ECR registries cache. | ||
if err = backoff.RetryNotify(ecrManager.buildCacheBackoff(), backoffSettings, notifyError); err != nil { | ||
log.Fatalf("Could not build ECR cache: %v", err) | ||
} | ||
|
||
workersNum := runtime.NumCPU() | ||
|
||
workerCh := make(chan []Repository, 5) | ||
var wg sync.WaitGroup | ||
|
||
for i := 0; i < workersNum; i++ { | ||
go worker(&wg, workerCh, &client, ecrManager) | ||
} | ||
|
||
// Add source repo and target repo pair to worker channel. | ||
for i := range config.SourceRepos { | ||
wg.Add(1) | ||
workerCh <- []Repository{config.SourceRepos[i], config.TargetRepos[i]} | ||
} | ||
|
||
wg.Wait() | ||
log.Println("Finished mirroring repositories") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you change is extended from aws-otel-collector release workflow in a separate repo. How are you going to do the operator release? will it override/duplicate the other releases on the collector? (eg, ECR, Dockerhub, S3)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Operator release will just mirror the related images from Quay and GCR and push them to two ECR registries -
adot-operator
andmirror-kube-rbac-proxy
. It won't affect the Collector release.