--- tags: fbc title: A simple template story --- # A simple template story In [OLM: File-based catalog Veneers](https://hackmd.io/ixIuRBNURV-7K4C1aGkzxQ), I brainstormed about the many directions OLM veener implementations could go. That document is more about vision and less about a practical next step. This document will focus more on a practical next step. ## What is a catalog template? A catalog template is a combination of two things: 1. An arbitrary API 2. An executable the reads (1) and produces a valid file-based catalog. ## File-based catalog (FBC) format For those who haven't actually seen the FBC format, here's a short yaml example. It has one package, one channel, and two bundles in that channel. ```yaml= schema: olm.package name: example-operator defaultChannel: stable --- schema: olm.channel package: example-operator name: stable entries: - name: example-operator.v0.1.0 - name: example-operator.v0.2.0 replaces: example-operator.v0.1.0 --- schema: olm.bundle package: example-operator name: example-operator.v0.1.0 image: quay.io/joelanford/example-operator-bundle:0.1.0 properties: - type: olm.gvk value: group: example.com kind: App version: v1 - type: olm.package value: packageName: example-operator version: 0.1.0 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoidjEiLCJraW5kIjoiU2VydmljZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJsYWJlbHMiOnsiY29udHJvbC1wbGFuZSI6ImNvbnRyb2xsZXItbWFuYWdlciJ9LCJuYW1lIjoiZXhhbXBsZS1vcGVyYXRvci1jb250cm9sbGVyLW1hbmFnZXItbWV0cmljcy1zZXJ2aWNlIn0sInNwZWMiOnsicG9ydHMiOlt7Im5hbWUiOiJodHRwcyIsInBvcnQiOjg0NDMsInByb3RvY29sIjoiVENQIiwidGFyZ2V0UG9ydCI6Imh0dHBzIn1dLCJzZWxlY3RvciI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdGF0dXMiOnsibG9hZEJhbGFuY2VyIjp7fX19 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoidjEiLCJkYXRhIjp7ImNvbnRyb2xsZXJfbWFuYWdlcl9jb25maWcueWFtbCI6ImFwaVZlcnNpb246IGNvbnRyb2xsZXItcnVudGltZS5zaWdzLms4cy5pby92MWFscGhhMVxua2luZDogQ29udHJvbGxlck1hbmFnZXJDb25maWdcbmhlYWx0aDpcbiAgaGVhbHRoUHJvYmVCaW5kQWRkcmVzczogOjgwODFcbm1ldHJpY3M6XG4gIGJpbmRBZGRyZXNzOiAxMjcuMC4wLjE6ODA4MFxud2ViaG9vazpcbiAgcG9ydDogOTQ0M1xubGVhZGVyRWxlY3Rpb246XG4gIGxlYWRlckVsZWN0OiB0cnVlXG4gIHJlc291cmNlTmFtZTogY2RmNjA0MTIuY29tXG4ifSwia2luZCI6IkNvbmZpZ01hcCIsIm1ldGFkYXRhIjp7Im5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLW1hbmFnZXItY29uZmlnIn19 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MSIsImtpbmQiOiJDbHVzdGVyUm9sZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoiZXhhbXBsZS1vcGVyYXRvci1tZXRyaWNzLXJlYWRlciJ9LCJydWxlcyI6W3sibm9uUmVzb3VyY2VVUkxzIjpbIi9tZXRyaWNzIl0sInZlcmJzIjpbImdldCJdfV19 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiZXhhbXBsZS5jb20vdjFcIixcbiAgICBcImtpbmRcIjogXCJBcHBcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcImFwcC1zYW1wbGVcIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwiZm9vXCI6IFwiYmFyXCJcbiAgICB9XG4gIH1cbl0iLCJjYXBhYmlsaXRpZXMiOiJCYXNpYyBJbnN0YWxsIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL2J1aWxkZXIiOiJvcGVyYXRvci1zZGstdjEuMTEuMCtnaXQiLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vcHJvamVjdF9sYXlvdXQiOiJnby5rdWJlYnVpbGRlci5pby92MyJ9LCJuYW1lIjoiZXhhbXBsZS1vcGVyYXRvci52MC4xLjAiLCJuYW1lc3BhY2UiOiJwbGFjZWhvbGRlciJ9LCJzcGVjIjp7ImFwaXNlcnZpY2VkZWZpbml0aW9ucyI6e30sImN1c3RvbXJlc291cmNlZGVmaW5pdGlvbnMiOnsib3duZWQiOlt7ImRlc2NyaXB0aW9uIjoiQXBwIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBhcHBzIEFQSSIsImRpc3BsYXlOYW1lIjoiQXBwIiwia2luZCI6IkFwcCIsIm5hbWUiOiJhcHBzLmV4YW1wbGUuY29tIiwidmVyc2lvbiI6InYxIn1dfSwiZGlzcGxheU5hbWUiOiJFeGFtcGxlIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJleGFtcGxlLmNvbSJdLCJyZXNvdXJjZXMiOlsiYXBwcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJleGFtcGxlLmNvbSJdLCJyZXNvdXJjZXMiOlsiYXBwcy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJleGFtcGxlLmNvbSJdLCJyZXNvdXJjZXMiOlsiYXBwcy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLWNvbnRyb2xsZXItbWFuYWdlciJ9XSwiZGVwbG95bWVudHMiOlt7Im5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLWNvbnRyb2xsZXItbWFuYWdlciIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjguMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6e319LHsiYXJncyI6WyItLWhlYWx0aC1wcm9iZS1iaW5kLWFkZHJlc3M9OjgwODEiLCItLW1ldHJpY3MtYmluZC1hZGRyZXNzPTEyNy4wLjAuMTo4MDgwIiwiLS1sZWFkZXItZWxlY3QiXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vam9lbGFuZm9yZC9leGFtcGxlLW9wZXJhdG9yOjAuMS4wIiwibGl2ZW5lc3NQcm9iZSI6eyJodHRwR2V0Ijp7InBhdGgiOiIvaGVhbHRoeiIsInBvcnQiOjgwODF9LCJpbml0aWFsRGVsYXlTZWNvbmRzIjoxNSwicGVyaW9kU2Vjb25kcyI6MjB9LCJuYW1lIjoibWFuYWdlciIsInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjIwMG0iLCJtZW1vcnkiOiIxMDBNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjBNaSJ9fSwic2VjdXJpdHlDb250ZXh0Ijp7ImFsbG93UHJpdmlsZWdlRXNjYWxhdGlvbiI6ZmFsc2V9fV0sInNlY3VyaXR5Q29udGV4dCI6eyJydW5Bc05vblJvb3QiOnRydWV9LCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiZXhhbXBsZS1vcGVyYXRvci1jb250cm9sbGVyLW1hbmFnZXIifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJwcm92aWRlciI6e30sInZlcnNpb24iOiIwLjEuMCJ9fQ== - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjYuMSJ9LCJjcmVhdGlvblRpbWVzdGFtcCI6bnVsbCwibmFtZSI6ImFwcHMuZXhhbXBsZS5jb20ifSwic3BlYyI6eyJncm91cCI6ImV4YW1wbGUuY29tIiwibmFtZXMiOnsia2luZCI6IkFwcCIsImxpc3RLaW5kIjoiQXBwTGlzdCIsInBsdXJhbCI6ImFwcHMiLCJzaW5ndWxhciI6ImFwcCJ9LCJzY29wZSI6Ik5hbWVzcGFjZWQiLCJ2ZXJzaW9ucyI6W3sibmFtZSI6InYxIiwic2NoZW1hIjp7Im9wZW5BUElWM1NjaGVtYSI6eyJkZXNjcmlwdGlvbiI6IkFwcCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgYXBwcyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IkFwcFNwZWMgZGVmaW5lcyB0aGUgZGVzaXJlZCBzdGF0ZSBvZiBBcHAiLCJwcm9wZXJ0aWVzIjp7ImZvbyI6eyJkZXNjcmlwdGlvbiI6IkZvbyBpcyBhbiBleGFtcGxlIGZpZWxkIG9mIEFwcC4gRWRpdCBhcHBfdHlwZXMuZ28gdG8gcmVtb3ZlL3VwZGF0ZSIsInR5cGUiOiJzdHJpbmcifX0sInR5cGUiOiJvYmplY3QifSwic3RhdHVzIjp7ImRlc2NyaXB0aW9uIjoiQXBwU3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIEFwcCIsInR5cGUiOiJvYmplY3QifX0sInR5cGUiOiJvYmplY3QifX0sInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6dHJ1ZSwic3VicmVzb3VyY2VzIjp7InN0YXR1cyI6e319fV19LCJzdGF0dXMiOnsiYWNjZXB0ZWROYW1lcyI6eyJraW5kIjoiIiwicGx1cmFsIjoiIn0sImNvbmRpdGlvbnMiOltdLCJzdG9yZWRWZXJzaW9ucyI6W119fQ== relatedImages: - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 name: "" - image: quay.io/joelanford/example-operator-bundle:0.1.0 name: "" - image: quay.io/joelanford/example-operator:0.1.0 name: "" --- schema: olm.bundle package: example-operator name: example-operator.v0.2.0 image: quay.io/joelanford/example-operator-bundle:0.2.0 properties: - type: olm.gvk value: group: example.com kind: App version: v1 - type: olm.package value: packageName: example-operator version: 0.2.0 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoidjEiLCJraW5kIjoiU2VydmljZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJsYWJlbHMiOnsiY29udHJvbC1wbGFuZSI6ImNvbnRyb2xsZXItbWFuYWdlciJ9LCJuYW1lIjoiZXhhbXBsZS1vcGVyYXRvci1jb250cm9sbGVyLW1hbmFnZXItbWV0cmljcy1zZXJ2aWNlIn0sInNwZWMiOnsicG9ydHMiOlt7Im5hbWUiOiJodHRwcyIsInBvcnQiOjg0NDMsInByb3RvY29sIjoiVENQIiwidGFyZ2V0UG9ydCI6Imh0dHBzIn1dLCJzZWxlY3RvciI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdGF0dXMiOnsibG9hZEJhbGFuY2VyIjp7fX19 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoidjEiLCJkYXRhIjp7ImNvbnRyb2xsZXJfbWFuYWdlcl9jb25maWcueWFtbCI6ImFwaVZlcnNpb246IGNvbnRyb2xsZXItcnVudGltZS5zaWdzLms4cy5pby92MWFscGhhMVxua2luZDogQ29udHJvbGxlck1hbmFnZXJDb25maWdcbmhlYWx0aDpcbiAgaGVhbHRoUHJvYmVCaW5kQWRkcmVzczogOjgwODFcbm1ldHJpY3M6XG4gIGJpbmRBZGRyZXNzOiAxMjcuMC4wLjE6ODA4MFxud2ViaG9vazpcbiAgcG9ydDogOTQ0M1xubGVhZGVyRWxlY3Rpb246XG4gIGxlYWRlckVsZWN0OiB0cnVlXG4gIHJlc291cmNlTmFtZTogY2RmNjA0MTIuY29tXG4ifSwia2luZCI6IkNvbmZpZ01hcCIsIm1ldGFkYXRhIjp7Im5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLW1hbmFnZXItY29uZmlnIn19 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MSIsImtpbmQiOiJDbHVzdGVyUm9sZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJuYW1lIjoiZXhhbXBsZS1vcGVyYXRvci1tZXRyaWNzLXJlYWRlciJ9LCJydWxlcyI6W3sibm9uUmVzb3VyY2VVUkxzIjpbIi9tZXRyaWNzIl0sInZlcmJzIjpbImdldCJdfV19 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiZXhhbXBsZS5jb20vdjFcIixcbiAgICBcImtpbmRcIjogXCJBcHBcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibmFtZVwiOiBcImFwcC1zYW1wbGVcIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwiZm9vXCI6IFwiYmFyXCJcbiAgICB9XG4gIH1cbl0iLCJjYXBhYmlsaXRpZXMiOiJCYXNpYyBJbnN0YWxsIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL2J1aWxkZXIiOiJvcGVyYXRvci1zZGstdjEuMTEuMCtnaXQiLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vcHJvamVjdF9sYXlvdXQiOiJnby5rdWJlYnVpbGRlci5pby92MyJ9LCJuYW1lIjoiZXhhbXBsZS1vcGVyYXRvci52MC4yLjAiLCJuYW1lc3BhY2UiOiJwbGFjZWhvbGRlciJ9LCJzcGVjIjp7ImFwaXNlcnZpY2VkZWZpbml0aW9ucyI6e30sImN1c3RvbXJlc291cmNlZGVmaW5pdGlvbnMiOnsib3duZWQiOlt7ImRlc2NyaXB0aW9uIjoiQXBwIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBhcHBzIEFQSSIsImRpc3BsYXlOYW1lIjoiQXBwIiwia2luZCI6IkFwcCIsIm5hbWUiOiJhcHBzLmV4YW1wbGUuY29tIiwidmVyc2lvbiI6InYxIn1dfSwiZGlzcGxheU5hbWUiOiJFeGFtcGxlIE9wZXJhdG9yIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyJleGFtcGxlLmNvbSJdLCJyZXNvdXJjZXMiOlsiYXBwcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJleGFtcGxlLmNvbSJdLCJyZXNvdXJjZXMiOlsiYXBwcy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJleGFtcGxlLmNvbSJdLCJyZXNvdXJjZXMiOlsiYXBwcy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsiYXV0aGVudGljYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJ0b2tlbnJldmlld3MiXSwidmVyYnMiOlsiY3JlYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJzdWJqZWN0YWNjZXNzcmV2aWV3cyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLWNvbnRyb2xsZXItbWFuYWdlciJ9XSwiZGVwbG95bWVudHMiOlt7Im5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLWNvbnRyb2xsZXItbWFuYWdlciIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiY29udGFpbmVycyI6W3siYXJncyI6WyItLXNlY3VyZS1saXN0ZW4tYWRkcmVzcz0wLjAuMC4wOjg0NDMiLCItLXVwc3RyZWFtPWh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8iLCItLWxvZ3Rvc3RkZXJyPXRydWUiLCItLXY9MTAiXSwiaW1hZ2UiOiJnY3IuaW8va3ViZWJ1aWxkZXIva3ViZS1yYmFjLXByb3h5OnYwLjguMCIsIm5hbWUiOiJrdWJlLXJiYWMtcHJveHkiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODQ0MywibmFtZSI6Imh0dHBzIiwicHJvdG9jb2wiOiJUQ1AifV0sInJlc291cmNlcyI6e319LHsiYXJncyI6WyItLWhlYWx0aC1wcm9iZS1iaW5kLWFkZHJlc3M9OjgwODEiLCItLW1ldHJpY3MtYmluZC1hZGRyZXNzPTEyNy4wLjAuMTo4MDgwIiwiLS1sZWFkZXItZWxlY3QiXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJpbWFnZSI6InF1YXkuaW8vam9lbGFuZm9yZC9leGFtcGxlLW9wZXJhdG9yOjAuMi4wIiwibGl2ZW5lc3NQcm9iZSI6eyJodHRwR2V0Ijp7InBhdGgiOiIvaGVhbHRoeiIsInBvcnQiOjgwODF9LCJpbml0aWFsRGVsYXlTZWNvbmRzIjoxNSwicGVyaW9kU2Vjb25kcyI6MjB9LCJuYW1lIjoibWFuYWdlciIsInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjIwMG0iLCJtZW1vcnkiOiIxMDBNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjBNaSJ9fSwic2VjdXJpdHlDb250ZXh0Ijp7ImFsbG93UHJpdmlsZWdlRXNjYWxhdGlvbiI6ZmFsc2V9fV0sInNlY3VyaXR5Q29udGV4dCI6eyJydW5Bc05vblJvb3QiOnRydWV9LCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJleGFtcGxlLW9wZXJhdG9yLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiZXhhbXBsZS1vcGVyYXRvci1jb250cm9sbGVyLW1hbmFnZXIifV19LCJzdHJhdGVneSI6ImRlcGxveW1lbnQifSwiaW5zdGFsbE1vZGVzIjpbeyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJwcm92aWRlciI6e30sInZlcnNpb24iOiIwLjIuMCJ9fQ== - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjYuMSJ9LCJjcmVhdGlvblRpbWVzdGFtcCI6bnVsbCwibmFtZSI6ImFwcHMuZXhhbXBsZS5jb20ifSwic3BlYyI6eyJncm91cCI6ImV4YW1wbGUuY29tIiwibmFtZXMiOnsia2luZCI6IkFwcCIsImxpc3RLaW5kIjoiQXBwTGlzdCIsInBsdXJhbCI6ImFwcHMiLCJzaW5ndWxhciI6ImFwcCJ9LCJzY29wZSI6Ik5hbWVzcGFjZWQiLCJ2ZXJzaW9ucyI6W3sibmFtZSI6InYxIiwic2NoZW1hIjp7Im9wZW5BUElWM1NjaGVtYSI6eyJkZXNjcmlwdGlvbiI6IkFwcCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgYXBwcyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuIFNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZCBtYXkgcmVqZWN0IHVucmVjb2duaXplZCB2YWx1ZXMuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy4gU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uIENhbm5vdCBiZSB1cGRhdGVkLiBJbiBDYW1lbENhc2UuIE1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IkFwcFNwZWMgZGVmaW5lcyB0aGUgZGVzaXJlZCBzdGF0ZSBvZiBBcHAiLCJwcm9wZXJ0aWVzIjp7ImJhciI6eyJkZXNjcmlwdGlvbiI6IkJhciBpcyBhbiBleGFtcGxlIGZpZWxkIG9mIEFwcC4iLCJ0eXBlIjoic3RyaW5nIn0sImZvbyI6eyJkZXNjcmlwdGlvbiI6IkZvbyBpcyBhbiBleGFtcGxlIGZpZWxkIG9mIEFwcC4iLCJ0eXBlIjoic3RyaW5nIn19LCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IkFwcFN0YXR1cyBkZWZpbmVzIHRoZSBvYnNlcnZlZCBzdGF0ZSBvZiBBcHAiLCJ0eXBlIjoib2JqZWN0In19LCJ0eXBlIjoib2JqZWN0In19LCJzZXJ2ZWQiOnRydWUsInN0b3JhZ2UiOnRydWUsInN1YnJlc291cmNlcyI6eyJzdGF0dXMiOnt9fX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpbXSwic3RvcmVkVmVyc2lvbnMiOltdfX0= relatedImages: - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 name: "" - image: quay.io/joelanford/example-operator-bundle:0.2.0 name: "" - image: quay.io/joelanford/example-operator:0.2.0 name: "" ``` You might be thinking, "Huh? That's not very short, and it looks pretty complicated, with all those properties and all of that base64 encoded stuff" And you're right. If you had to maintain every byte of this file directly, it would be painful. But there's good news! Most of what you see here can be easily derived for you. ## The simplest template "Cool!", you say. "Show me!" ```yaml= --- schema: olm.package name: example-operator defaultChannel: stable --- schema: olm.channel package: example-operator name: stable entries: - name: example-operator.v0.1.0 - name: example-operator.v0.2.0 replaces: example-operator.v0.1.0 --- schema: olm.bundle image: quay.io/joelanford/example-operator-bundle:0.1.0 --- schema: olm.bundle image: quay.io/joelanford/example-operator-bundle:0.2.0 ``` How does that look? Much easier right? Here's what changed: we simply remove every field from the `olm.bundle` blobs except for the `schema` and `image`. You quickly run `opm validate` on this and to your dismay, it errors out: ``` FATA[0000] package name must be set for bundle "" ``` So the question in your head is now probably, "Alright, well how do I derive the rest of that stuff to get back to a valid catalog?" And the answer is simple. We need a tool that: 1. Takes this file as input 2. For each `olm.bundle` blob, runs `opm render {.image}` 3. Replaces the original sparse blob with the new rendered blob. 4. Writes the new contents to stdout. What do you know, I have that tool right here, in just a few lines of bash: **convert.sh** ```bash= #!/bin/bash set -eu -o pipefail out=$(cat $1) while IFS= read -r img; do export rendered=$(opm render "$img" -o yaml) out=$(echo "$out" | yq eval-all "(select(.schema == \"olm.bundle\" and .image == \"$img\")) |= env(rendered)" -) done <<< $(echo "$out" | yq eval-all "select(.schema == \"olm.bundle\") | [.image][]" -) echo "$out" ``` Putting that into a pipeline to build and push the catalog is pretty simple too: **build.sh** ```bash= set -eu -o pipefail img=$1 mkdir catalog ./convert.sh veneer.yaml > catalog/catalog.yaml opm validate catalog opm alpha generate dockerfile catalog docker build -t $1 -f catalog.Dockerfile . docker push $1 rm -r catalog catalog.Dockerfile ``` In that command we: 1. Create a temporary FBC root directory for our package called "catalog". 2. Run our template conversion tool and produce "catalog/catalog.yaml". 3. Run `opm validate catalog` to ensure our generated catalog is valid. 4. Run `opm alpha generate dockerfile catalog` to create a Dockerfile. 5. Run `docker build` and `docker push` to build and push our image. 6. Cleanup the temporary catalog directory. :::success :tada: :tada: :tada: We have our first template! :tada: :tada: :tada: ::: ## Prototype Repository https://github.com/joelanford/example-operator-index/ #### Sanity checks PR: https://github.com/joelanford/example-operator-index/pull/1 - ❌ [Fail (no channel entry)](https://github.com/joelanford/example-operator-index/runs/4857567701?check_suite_focus=true#step:3:8) - ❌ [Fail (multiple channel heads)](https://github.com/joelanford/example-operator-index/runs/4857584149?check_suite_focus=true#step:3:8) - ✅ [Success](https://github.com/joelanford/example-operator-index/runs/4857597886?check_suite_focus=true#step:3:7)