package pallet import ( "fmt" wh "edge-infra.dev/pkg/f8n/warehouse" "edge-infra.dev/pkg/f8n/warehouse/oci" ) var ( // errInvalidMetadata occurs when a pallet's metadata is invalid or incomplete. errInvalidMetadata = fmt.Errorf("invalid pallet metadata") ) // K8s Annotations are added to every K8s object bundled into a pallet package. const ( KnnotationSource = "pallet.edge.ncr.com/source" KnnotationCreated = "pallet.edge.ncr.com/created" KnnotationTeam = "pallet.edge.ncr.com/team" KnnotationName = "pallet.edge.ncr.com/name" KnnotationVersion = "pallet.edge.ncr.com/version" KnnotationVendor = "pallet.edge.ncr.com/vendor" KnnotationRevision = "pallet.edge.ncr.com/revision" ) // Metadata defines information about the package itself. type Metadata struct { // Required Name string Team string Vendor string BuildInfo // Optional Description string Readme string } // BuildInfo is build information that gets added to the produced artifacts using // predefined OCI annotations: // https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys type BuildInfo struct { Source string Version string Revision string Created string } func (b *BuildInfo) Validate() error { if b.Source == "" { return fmt.Errorf("%w: source must be provided", errInvalidMetadata) } if b.Revision == "" { return fmt.Errorf("%w: revision must be provided", errInvalidMetadata) } if b.Version == "" { return fmt.Errorf("%w: version must be provided", errInvalidMetadata) } if b.Created == "" { return fmt.Errorf("%w: created must be provided", errInvalidMetadata) } return nil } func (m *Metadata) Validate() error { if m.Name == "" { return fmt.Errorf("%w: name must be provided", errInvalidMetadata) } if m.Team == "" { return fmt.Errorf("%w: team must be provided", errInvalidMetadata) } return m.BuildInfo.Validate() } // OCIAnnotations translates the contents of the Meatdata struct to a map of // appropriate OCI artifact annotations. Where possible, it maps data to // existing OCI annotations (https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/annotations.go), // but extends them with Warehouse specific annotations where appropriate. func (m Metadata) OCIAnnotations() map[string]string { // set a default vendor if one is not provided. if m.Vendor == "" { m.Vendor = "NCR" } r := map[string]string{ wh.AnnotationSource: m.Source, wh.AnnotationVersion: m.Version, wh.AnnotationCreated: m.Created, wh.AnnotationRevision: m.Revision, wh.AnnotationVendor: m.Vendor, wh.AnnotationTitle: m.Name, // Additionally store name using the Pallet-specific annotation so that usage // doesn't compete with integrations based on v1.AnnotationTitle in the future wh.AnnotationName: m.Name, wh.AnnotationTeam: m.Team, } // Add optional values if they are present if m.Description != "" { r[wh.AnnotationDescription] = m.Description } if m.Readme != "" { r[wh.AnnotationDocumentation] = m.Readme } return r } func (m Metadata) K8sAnnotations() map[string]string { r := map[string]string{ KnnotationTeam: m.Team, KnnotationName: m.Name, KnnotationVersion: m.Version, KnnotationRevision: m.Revision, } if m.Created != "" { r[KnnotationCreated] = m.Created } if m.Source != "" { r[KnnotationSource] = m.Source } return r } var requiredMetaAnnos = []string{ wh.AnnotationTeam, wh.AnnotationName, wh.AnnotationRevision, wh.AnnotationSource, wh.AnnotationVersion, wh.AnnotationCreated, wh.AnnotationVendor, } // metadataFromAnnotations creates a Metadata struct from an annotations map // pulled from a valid pallet OCI artifact. If the input map does not contain // all of the required annotations, an error is returned func metadataFromAnnotations(aa map[string]string) (Metadata, error) { if _, ok := aa[wh.AnnotationVendor]; !ok { aa[wh.AnnotationVendor] = "NCR" } for _, a := range requiredMetaAnnos { if _, ok := aa[a]; !ok { return Metadata{}, fmt.Errorf( "%w: invalid pallet metadata: required annotation %s not found", oci.ErrInvalidArtifact, a, ) } } meta := Metadata{ Name: aa[wh.AnnotationName], Team: aa[wh.AnnotationTeam], BuildInfo: BuildInfo{ Version: aa[wh.AnnotationVersion], Source: aa[wh.AnnotationSource], Created: aa[wh.AnnotationCreated], Revision: aa[wh.AnnotationRevision], }, Vendor: aa[wh.AnnotationVendor], } for _, a := range aa { switch a { case wh.AnnotationDocumentation: meta.Readme = aa[wh.AnnotationDocumentation] case wh.AnnotationDescription: meta.Description = aa[wh.AnnotationDescription] } } return meta, nil }