Skip to content
This repository was archived by the owner on Jun 14, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 4 additions & 18 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,39 +97,25 @@ tag_specification:
cluster: https://api.ci.openshift.org
name: origin-v3.11
namespace: openshift
tag: ''
tag_overrides: {}
```

There are two primary modes for assembling a release:
- single `ImageStream`, multiple tags (`openshift/origin-v3.9:control-plane`)
- multiple `ImageStream`s, one tag (`openshift/origin-control-plane:v3.9`)

The former works well for central control, the latter for distributed control:
when many disparate processes are publishing images at their own cadences, each
process can own its own `ImageStream` and coordination between processes can be
through coordination in `ImageStreamTag`s. When one process marshalls a release,
the process can push to one `ImageStream` under multiple `ImageStreamTags`. In
practice, the OpenShift releases are assembled using the former approach, while
non-release images like infrastructure tooling, _etc_, are assembled using the
latter.
The release tag specification points to an image stream containing multiple tags,
each of which references a single component by a well known name, e.g.
`openshift/origin-v3.9:control-plane`.

## `tag_specification.cluster`
`cluster` is an optional cluster string (`host`, `host:port`, or `scheme://host:port`)
to connect to for the `ImageStream`. The referenced OpenShift cluster must support
anonymous access to retrieve `ImageStream`s, `ImageStreamTag`s, and
`ImageStreamImage`s in the provided namespace.

## `tag_specification.tag`
`tag` is used to specify the single tag when multiple `ImageStreams` but one tag
are used to assemble a release.

## `tag_specification.namespace`
`namespace` determines the `Namespace` on the target cluster where release
`ImageStreams` are located.

## `tag_specification.name`
`name` is the `ImageStream` name when a single `ImageStream` but multiple
`name` is the `ImageStream` name where a single `ImageStream` and multiple
tags are used to assemble a release.

## `tag_specification.tag_overrides`
Expand Down
14 changes: 7 additions & 7 deletions pkg/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ func validatePromotionWithTagSpec(promotion *PromotionConfiguration, tagSpec *Re
if len(promotion.Namespace) == 0 && len(tagSpec.Namespace) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("promotion: no namespace defined"))
}
if len(promotion.Name) == 0 && len(promotion.Tag) == 0 {
if len(tagSpec.Name) != 0 || len(tagSpec.Tag) != 0 {
if len(promotion.Name) == 0 {
if len(tagSpec.Name) != 0 {
// will get defaulted, is ok
} else {
validationErrors = append(validationErrors, errors.New("promotion: no name or tag provided and could not derive defaults from tag_specification"))
validationErrors = append(validationErrors, errors.New("promotion: no name provided and could not derive defaults from tag_specification"))
}
}

Expand Down Expand Up @@ -156,8 +156,8 @@ func validatePromotionConfiguration(fieldRoot string, input PromotionConfigurati
validationErrors = append(validationErrors, fmt.Errorf("%s: no namespace defined", fieldRoot))
}

if len(input.Name) == 0 && len(input.Tag) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("%s: no name or tag defined", fieldRoot))
if len(input.Name) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("%s: no name defined", fieldRoot))
}
return validationErrors
}
Expand All @@ -169,8 +169,8 @@ func validateReleaseTagConfiguration(fieldRoot string, input ReleaseTagConfigura
validationErrors = append(validationErrors, fmt.Errorf("%s: no namespace defined", fieldRoot))
}

if len(input.Name) == 0 && len(input.Tag) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("%s: no name or tag defined", fieldRoot))
if len(input.Name) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("%s: no name defined", fieldRoot))
}
return validationErrors
}
Expand Down
26 changes: 7 additions & 19 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,9 @@ type ImageStreamTagReference struct {
}

// ReleaseTagConfiguration describes how a release is
// assembled from release artifacts. There are two primary modes,
// single stream, multiple tags (openshift/origin-v3.9:control-plane)
// on one stream, or multiple streams with one tag
// (openshift/origin-control-plane:v3.9). The former works well for
// central control, the latter for distributed control.
// assembled from release artifacts. A release image stream is a
// single stream with multiple tags (openshift/origin-v3.9:control-plane),
// each tag being a unique and well defined name for a component.
type ReleaseTagConfiguration struct {
// Cluster is an optional cluster string (host, host:port, or
// scheme://host:port) to connect to for this image stream. The
Expand All @@ -170,15 +168,10 @@ type ReleaseTagConfiguration struct {
// job are tagged from.
Namespace string `json:"namespace"`

// Name is an optional image stream name to use that
// contains all component tags. If specified, tag is
// ignored.
// Name is the image stream name to use that contains all
// component tags.
Name string `json:"name"`

// Tag is the ImageStreamTag tagged in for each
// ImageStream in the above Namespace.
Tag string `json:"tag,omitempty"`

// NamePrefix is prepended to the final output image name
// if specified.
NamePrefix string `json:"name_prefix,omitempty"`
Expand All @@ -198,15 +191,10 @@ type PromotionConfiguration struct {
// artifacts will be published to.
Namespace string `json:"namespace"`

// Name is an optional image stream name to use that
// contains all component tags. If specified, tag is
// ignored.
// Name is the image stream name to use that
// contains all component tags.
Name string `json:"name"`

// Tag is the ImageStreamTag tagged in for each
// build image's ImageStream.
Tag string `json:"tag,omitempty"`

// NamePrefix is prepended to the final output image name
// if specified.
NamePrefix string `json:"name_prefix,omitempty"`
Expand Down
27 changes: 8 additions & 19 deletions pkg/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,25 +361,14 @@ func stepConfigsForBuild(config *api.ReleaseBuildConfiguration, jobSpec *api.Job
image := &config.Images[i]
buildSteps = append(buildSteps, api.StepConfiguration{ProjectDirectoryImageBuildStepConfiguration: image})
if config.ReleaseTagConfiguration != nil {
if len(config.ReleaseTagConfiguration.Name) > 0 {
buildSteps = append(buildSteps, api.StepConfiguration{OutputImageTagStepConfiguration: &api.OutputImageTagStepConfiguration{
From: image.To,
To: api.ImageStreamTagReference{
Name: fmt.Sprintf("%s%s", config.ReleaseTagConfiguration.NamePrefix, api.StableImageStream),
Tag: string(image.To),
},
Optional: image.Optional,
}})
} else {
buildSteps = append(buildSteps, api.StepConfiguration{OutputImageTagStepConfiguration: &api.OutputImageTagStepConfiguration{
From: image.To,
To: api.ImageStreamTagReference{
Name: string(image.To),
Tag: "ci",
},
Optional: image.Optional,
}})
}
buildSteps = append(buildSteps, api.StepConfiguration{OutputImageTagStepConfiguration: &api.OutputImageTagStepConfiguration{
From: image.To,
To: api.ImageStreamTagReference{
Name: fmt.Sprintf("%s%s", config.ReleaseTagConfiguration.NamePrefix, api.StableImageStream),
Tag: string(image.To),
},
Optional: image.Optional,
}})
} else {
buildSteps = append(buildSteps, api.StepConfiguration{OutputImageTagStepConfiguration: &api.OutputImageTagStepConfiguration{
From: image.To,
Expand Down
129 changes: 34 additions & 95 deletions pkg/steps/release/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ type promotionStep struct {
}

func targetName(config api.PromotionConfiguration) string {
if len(config.Name) > 0 {
return fmt.Sprintf("%s/%s:${component}", config.Namespace, config.Name)
}
return fmt.Sprintf("%s/${component}:%s", config.Namespace, config.Tag)
return fmt.Sprintf("%s/%s:%s", config.Namespace, config.Name, api.ComponentFormatReplacement)
}

func (s *promotionStep) Inputs(ctx context.Context, dry bool) (api.InputDefinition, error) {
Expand Down Expand Up @@ -71,107 +68,49 @@ func (s *promotionStep) Run(ctx context.Context, dry bool) error {
return fmt.Errorf("could not resolve pipeline imagestream: %v", err)
}

if len(s.config.Name) > 0 {
return retry.RetryOnConflict(promotionRetry, func() error {
is, err := s.dstClient.ImageStreams(s.config.Namespace).Get(s.config.Name, meta.GetOptions{})
if errors.IsNotFound(err) {
is, err = s.dstClient.ImageStreams(s.config.Namespace).Create(&imageapi.ImageStream{
ObjectMeta: meta.ObjectMeta{
Name: s.config.Name,
Namespace: s.config.Namespace,
},
})
}
if err != nil {
return fmt.Errorf("could not retrieve target imagestream: %v", err)
}

for dst, src := range tags {
if valid, _ := findStatusTag(pipeline, src); valid != nil {
is.Spec.Tags = append(is.Spec.Tags, imageapi.TagReference{
Name: dst,
From: valid,
})
}
}

if dry {
istJSON, err := json.MarshalIndent(is, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal image stream: %v", err)
}
fmt.Printf("%s\n", istJSON)
return nil
}
if _, err := s.dstClient.ImageStreams(s.config.Namespace).Update(is); err != nil {
if errors.IsConflict(err) {
return err
}
return fmt.Errorf("could not promote image streams: %v", err)
}
return nil
})
if len(s.config.Name) == 0 {
return fmt.Errorf("name is a required field for release tagging")
}

client := s.dstClient.ImageStreamTags(s.config.Namespace)
for dst, src := range tags {
valid, _ := findStatusTag(pipeline, src)
if valid == nil {
continue
}

name := fmt.Sprintf("%s%s", s.config.NamePrefix, dst)

err := retry.RetryOnConflict(promotionRetry, func() error {
_, err := s.dstClient.ImageStreams(s.config.Namespace).Get(name, meta.GetOptions{})
if errors.IsNotFound(err) {
_, err = s.dstClient.ImageStreams(s.config.Namespace).Create(&imageapi.ImageStream{
ObjectMeta: meta.ObjectMeta{
Name: name,
Namespace: s.config.Namespace,
},
Spec: imageapi.ImageStreamSpec{
LookupPolicy: imageapi.ImageLookupPolicy{
Local: true,
},
},
})
}
if err != nil {
return fmt.Errorf("could not ensure target imagestream: %v", err)
}

ist := &imageapi.ImageStreamTag{
return retry.RetryOnConflict(promotionRetry, func() error {
is, err := s.dstClient.ImageStreams(s.config.Namespace).Get(s.config.Name, meta.GetOptions{})
if errors.IsNotFound(err) {
is, err = s.dstClient.ImageStreams(s.config.Namespace).Create(&imageapi.ImageStream{
ObjectMeta: meta.ObjectMeta{
Name: fmt.Sprintf("%s:%s", name, s.config.Tag),
Name: s.config.Name,
Namespace: s.config.Namespace,
},
Tag: &imageapi.TagReference{
Name: s.config.Tag,
})
}
if err != nil {
return fmt.Errorf("could not retrieve target imagestream: %v", err)
}

for dst, src := range tags {
if valid, _ := findStatusTag(pipeline, src); valid != nil {
is.Spec.Tags = append(is.Spec.Tags, imageapi.TagReference{
Name: dst,
From: valid,
},
}
if dry {
istJSON, err := json.MarshalIndent(ist, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal imagestreamtag: %v", err)
}
fmt.Printf("%s\n", istJSON)
return nil
})
}
if _, err := client.Update(ist); err != nil {
if errors.IsConflict(err) {
return err
}
return fmt.Errorf("could not promote imagestreamtag %s: %v", dst, err)
}

if dry {
istJSON, err := json.MarshalIndent(is, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal image stream: %v", err)
}
fmt.Printf("%s\n", istJSON)
return nil
})
if err != nil {
return err
}
}
return nil
if _, err := s.dstClient.ImageStreams(s.config.Namespace).Update(is); err != nil {
if errors.IsConflict(err) {
return err
}
return fmt.Errorf("could not promote image streams: %v", err)
}
return nil
})
}

func (s *promotionStep) Done() (bool, error) {
Expand Down
Loading