-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Teach oc adm release extract
to checkout Git repos
#22030
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package release | ||
|
||
const ( | ||
// This annotation is set in image-references when created with --from-release. | ||
annotationReleaseFromRelease = "release.openshift.io/from-release" | ||
// This annotation is set in image-references when created with --from-image-stream. | ||
annotationReleaseFromImageStream = "release.openshift.io/from-image-stream" | ||
|
||
// This value is set on images as LABEL to 'true' to indicate they should be | ||
// scanned for a /manifests/ directory to contribute to the payload. | ||
annotationReleaseOperator = "io.openshift.release.operator" | ||
|
||
// This is an internal annotation to indicate the source image was not derived | ||
// from an image stream or existing release but was manually specified. | ||
annotationReleaseOverride = "io.openshift.release.override" | ||
// This LABEL is set on images to indicate the manifest digest that was used | ||
// as the base layer for the release image (usually the cluster-version-operator). | ||
annotationReleaseBaseImageDigest = "io.openshift.release.base-image-digest" | ||
// This LABEL is a comma-delimited list of key=version pairs that can be consumed | ||
// by other manifests within the payload to hardcode version strings. Version must | ||
// be a semantic version with no build label (+ is not allowed) and key must be | ||
// alphanumeric characters and dashes only. The value `0.0.1-snapshot-key` in a | ||
// manifest will be substituted with the version value for key. | ||
annotationBuildVersions = "io.openshift.build.versions" | ||
|
||
// This LABEL is the git ref that an image was built with. Copied unmodified to | ||
// the image-references file. | ||
annotationBuildSourceRef = "io.openshift.build.commit.ref" | ||
// This LABEL is the full git commit hash that an image was built with. Copied | ||
// unmodified to the image-references file. | ||
annotationBuildSourceCommit = "io.openshift.build.commit.id" | ||
// This LABEL is the git clone location that an image was built with. Copied | ||
// unmodified to the image-references file. | ||
annotationBuildSourceLocation = "io.openshift.build.source-location" | ||
|
||
urlGithubPrefix = "https://github.com/" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,13 +7,18 @@ import ( | |
"os" | ||
"time" | ||
|
||
"github.com/golang/glog" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
digest "github.com/opencontainers/go-digest" | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" | ||
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" | ||
|
||
configv1client "github.com/openshift/client-go/config/clientset/versioned" | ||
"github.com/openshift/origin/pkg/image/apis/image/docker10" | ||
imagereference "github.com/openshift/origin/pkg/image/apis/image/reference" | ||
"github.com/openshift/origin/pkg/oc/cli/image/extract" | ||
|
@@ -38,15 +43,22 @@ func NewExtract(f kcmdutil.Factory, parentName string, streams genericclioptions | |
debugging. Update images contain manifests and metadata about the operators that | ||
must be installed on the cluster for a given version. | ||
|
||
Instead of extracting the manifests, you can specify --git=DIR to perform a Git | ||
checkout of the source code that comprises the release. A warning will be printed | ||
if the component is not associated with source code. The command will not perform | ||
any destructive actions on your behalf except for executing a 'git checkout' which | ||
may change the current branch. Requires 'git' to be on your path. | ||
|
||
Experimental: This command is under active development and may change without notice. | ||
`), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
kcmdutil.CheckErr(o.Complete(cmd, args)) | ||
kcmdutil.CheckErr(o.Complete(f, cmd, args)) | ||
kcmdutil.CheckErr(o.Run()) | ||
}, | ||
} | ||
flags := cmd.Flags() | ||
flags.StringVarP(&o.RegistryConfig, "registry-config", "a", o.RegistryConfig, "Path to your registry credentials (defaults to ~/.docker/config.json)") | ||
flags.StringVar(&o.GitExtractDir, "git", o.GitExtractDir, "Check out the sources that created this release into the provided dir. Repos will be created at <dir>/<host>/<path>. Requires 'git' on your path.") | ||
flags.StringVar(&o.From, "from", o.From, "Image containing the release payload.") | ||
flags.StringVar(&o.File, "file", o.File, "Extract a single file from the payload to standard output.") | ||
flags.StringVar(&o.Directory, "to", o.Directory, "Directory to write release contents to, defaults to the current directory.") | ||
|
@@ -58,6 +70,9 @@ type ExtractOptions struct { | |
|
||
From string | ||
|
||
// GitExtractDir is the path of a root directory to extract the source of a release to. | ||
GitExtractDir string | ||
|
||
Directory string | ||
File string | ||
|
||
|
@@ -66,7 +81,39 @@ type ExtractOptions struct { | |
ImageMetadataCallback func(m *extract.Mapping, dgst digest.Digest, config *docker10.DockerImageConfig) | ||
} | ||
|
||
func (o *ExtractOptions) Complete(cmd *cobra.Command, args []string) error { | ||
func (o *ExtractOptions) Complete(f kcmdutil.Factory, cmd *cobra.Command, args []string) error { | ||
switch { | ||
case len(args) == 0 && len(o.From) == 0: | ||
cfg, err := f.ToRESTConfig() | ||
if err != nil { | ||
return fmt.Errorf("info expects one argument, or a connection to an OpenShift 4.x server: %v", err) | ||
} | ||
client, err := configv1client.NewForConfig(cfg) | ||
if err != nil { | ||
return fmt.Errorf("info expects one argument, or a connection to an OpenShift 4.x server: %v", err) | ||
} | ||
cv, err := client.Config().ClusterVersions().Get("version", metav1.GetOptions{}) | ||
if err != nil { | ||
if apierrors.IsNotFound(err) { | ||
return fmt.Errorf("you must be connected to an OpenShift 4.x server to fetch the current version") | ||
} | ||
return fmt.Errorf("info expects one argument, or a connection to an OpenShift 4.x server: %v", err) | ||
} | ||
image := cv.Status.Desired.Image | ||
if len(image) == 0 && cv.Spec.DesiredUpdate != nil { | ||
image = cv.Spec.DesiredUpdate.Image | ||
} | ||
if len(image) == 0 { | ||
return fmt.Errorf("the server is not reporting a release image at this time, please specify an image to extract") | ||
} | ||
o.From = image | ||
|
||
case len(args) == 1 && len(o.From) > 0, len(args) > 1: | ||
return fmt.Errorf("you may only specify a single image via --from or argument") | ||
|
||
case len(args) == 1: | ||
o.From = args[0] | ||
} | ||
return nil | ||
} | ||
|
||
|
@@ -78,6 +125,10 @@ func (o *ExtractOptions) Run() error { | |
return fmt.Errorf("only one of --to and --file may be set") | ||
} | ||
|
||
if len(o.GitExtractDir) > 0 { | ||
return o.extractGit(o.GitExtractDir) | ||
} | ||
|
||
dir := o.Directory | ||
if err := os.MkdirAll(dir, 0755); err != nil { | ||
return err | ||
|
@@ -147,3 +198,80 @@ func (o *ExtractOptions) Run() error { | |
return opts.Run() | ||
} | ||
} | ||
|
||
func (o *ExtractOptions) extractGit(dir string) error { | ||
if err := os.MkdirAll(dir, 0750); err != nil { | ||
return err | ||
} | ||
|
||
release, err := NewInfoOptions(o.IOStreams).LoadReleaseInfo(o.From) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cloner := &git{} | ||
|
||
hadErrors := false | ||
alreadyExtracted := make(map[string]string) | ||
for _, ref := range release.References.Spec.Tags { | ||
repo := ref.Annotations[annotationBuildSourceLocation] | ||
commit := ref.Annotations[annotationBuildSourceCommit] | ||
if len(repo) == 0 || len(commit) == 0 { | ||
if glog.V(2) { | ||
glog.Infof("Tag %s has no source info", ref.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. nit: i would print the 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. Why? 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. because maybe there is a typo but this won’t tell me where |
||
} else { | ||
fmt.Fprintf(o.ErrOut, "warning: Tag %s has no source info\n", ref.Name) | ||
} | ||
continue | ||
} | ||
if oldCommit, ok := alreadyExtracted[repo]; ok { | ||
if oldCommit != commit { | ||
fmt.Fprintf(o.ErrOut, "warning: Repo %s referenced more than once with different commits, only checking out the first reference\n", repo) | ||
} | ||
continue | ||
} | ||
alreadyExtracted[repo] = commit | ||
|
||
basePath, err := sourceLocationAsRelativePath(dir, repo) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var extractedRepo *git | ||
fi, err := os.Stat(basePath) | ||
if err != nil { | ||
if !os.IsNotExist(err) { | ||
return err | ||
} | ||
if err := os.MkdirAll(basePath, 0750); err != nil { | ||
return err | ||
} | ||
} else { | ||
if !fi.IsDir() { | ||
return fmt.Errorf("repo path %s is not a directory", basePath) | ||
} | ||
} | ||
extractedRepo, err = cloner.ChangeContext(basePath) | ||
if err != nil { | ||
if err != noSuchRepo { | ||
return err | ||
} | ||
glog.V(2).Infof("Cloning %s ...", repo) | ||
if err := extractedRepo.Clone(repo, o.Out, o.ErrOut); err != nil { | ||
hadErrors = true | ||
fmt.Fprintf(o.ErrOut, "error: cloning %s: %v\n", repo, err) | ||
continue | ||
} | ||
} | ||
glog.V(2).Infof("Checkout %s from %s ...", commit, repo) | ||
if err := extractedRepo.CheckoutCommit(repo, commit); err != nil { | ||
hadErrors = true | ||
fmt.Fprintf(o.ErrOut, "error: checking out commit for %s: %v\n", repo, err) | ||
continue | ||
} | ||
} | ||
if hadErrors { | ||
return kcmdutil.ErrExit | ||
} | ||
return nil | ||
} |
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.
Maybe worth adding a warning we're looking into new version and not the current one.
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.
That's not a warning. The desired state is what the cluster shows.
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.
Also, you can have a desired state without a valid status (although it would be rare)