@spinnaker/docker 0.0.147 → 2025.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,2 +1,976 @@
1
- import{RetryService as e,REST as t,AccountService as a,ValidationMessage as s,HelpField as i,Tooltip as n,SETTINGS as o,Registry as r,BakeExecutionLabel as l,AuthenticationService as c,BakeryReader as g,TetheredSelect as d,Spinner as m}from"@spinnaker/core";import p,{groupBy as u,reduce as h,uniq as f,trim as v,get as y}from"lodash";import b from"react";import E from"react-select";import{module as k}from"angular";import N from"@uirouter/angularjs";import{$q as I}from"ngimport";import{Subject as C,from as x}from"rxjs";import{debounceTime as z,switchMap as S}from"rxjs/operators";class L{static getImage(e,a,s){return t("/images").path(s,a,e).query({provider:"docker"}).get().then((e=>e&&e.length?e[0]:null)).catch((()=>null))}static findImages(a){return e.buildRetrySequence((()=>t("/images/find").query(a).get()),(e=>e.length>0),10,1e3).then((e=>e)).catch((()=>[]))}static findTags(a){return e.buildRetrySequence((()=>t("/images/tags").query(a).get()),(e=>e.length>0),10,1e3).then((e=>e)).catch((()=>[]))}}class w{static splitImageId(e=""){let t;t=e.includes("@")?e.split("@"):e.split(":");const a=t[0],s=a.split("/").slice(0,-1).join("/"),i=t.length>1?t.slice(1).join(":"):"";let n,o;return i&&(i.startsWith("sha256:")?o=i:n=i),{organization:s,repository:a,digest:o,tag:n}}static generateImageId(e){if(!e.repository||!e.digest&&!e.tag)return;let t;return t=e.digest?`${e.repository}@${e.digest}`:`${e.repository}:${e.tag}`,t}}const _=["organization","repository","tag","digest"],O=[{label:"Manually",value:!0},{label:"Select from list",value:!1}];class T extends b.Component{constructor(e){super(e),this.unmounted=!1,this.cachedValues={},this.handleRefreshImages=()=>{this.refreshImages(this.props)},this.lookupTypeChanged=e=>{const t=e.value,a=this.state.lookupType,s=this.props[a],i=this.cachedValues[t];this.valueChanged(a,void 0),this.cachedValues[t]&&this.valueChanged(t,i),this.setState({lookupType:t}),this.cachedValues[a]=s},this.showManualInput=e=>{if(!e){const e=w.splitImageId(this.props.imageId||"");this.props.onChange(e),this.state.switchedManualWarning&&this.setState({switchedManualWarning:void 0,missingFields:void 0})}this.setState({defineManually:e})};const t=e.account?[{label:e.account,value:e.account}]:[],a=e.organization&&e.organization.length?[{label:e.organization,value:e.organization}]:[],s=e.repository&&e.repository.length?[{label:e.repository,value:e.repository}]:[],i=e.tag&&e.tag.length?[{label:e.tag,value:e.tag}]:[],n=w.splitImageId(e.imageId),o=e.allowManualDefinition&&Boolean(e.imageId&&e.imageId.includes("${"));this.state={accountOptions:t,switchedManualWarning:void 0,imagesLoaded:!1,imagesLoading:!1,organizationOptions:a,repositoryOptions:s,defineManually:o,tagOptions:i,lookupType:e.digest||n.digest?"digest":"tag"}}getAccountMap(e){const t=u(e.filter((e=>e.account)),"account");return h(t,((e,t,a)=>(e[a]=f(t.map((e=>`${e.repository.split("/").slice(0,-1).join("/")}`))),e)),{})}getRegistryMap(e){return e.reduce(((e,t)=>(e[t.account]=t.registry,e)),{})}getOrganizationMap(e){const t=u(e.filter((e=>e.repository)),(e=>`${e.account}/${e.repository.split("/").slice(0,-1).join("/")}`));return h(t,((e,t,a)=>(e[a]=f(t.map((e=>e.repository))),e)),{})}getRepositoryMap(e){const t=u(e.filter((e=>e.account)),"repository");return h(t,((e,t,a)=>(e[a]=f(t.map((e=>e.tag))),e)),{})}getOrganizationsList(e){return e&&e[this.props.showRegistry?this.props.account:this.props.registry]||[]}getRepositoryList(e,t,a){if(e){return e[`${this.props.showRegistry?this.props.account:a}/${t||""}`]||[]}return[]}getTags(e,t,a){let s=[];return this.props.specifyTagByRegex?e&&""===v(e)&&(e=void 0):t&&(s=t[a]||[],s.includes(e)||!e||e.includes("${")||(e=void 0)),{tag:e,tags:s}}componentWillReceiveProps(e){!this.images||["account","showRegistry"].some((t=>this.props[t]!==e[t]))?this.refreshImages(e):["organization","registry","repository"].some((t=>this.props[t]!==e[t]))&&this.updateThings(e),e.imageId&&e.imageId.includes("${")&&this.setState({defineManually:!0})}componentWillUnmount(){this.unmounted=!0}synchronizeChanges(e,t){const{organization:a,repository:s,tag:i,digest:n}=e;if(this.props.onChange){const e=w.generateImageId({organization:a,repository:s,tag:i,digest:n}),o={};i!==this.props.tag&&(o.tag=i),e!==this.props.imageId&&(o.imageId=e),a!==this.props.organization&&(o.organization=a),t!==this.props.registry&&(o.registry=t),s!==this.props.repository&&(o.repository=s),n!==this.props.digest&&(o.digest=n),Object.keys(o).length>0&&this.props.onChange(o)}}updateThings(e,t=!1){if(!this.repositoryMap||this.unmounted)return;const{imageId:a,specifyTagByRegex:s}=e;let{organization:i,registry:n,repository:o}=e;e.showRegistry&&(n=this.registryMap[e.account]);const r=!i||this.organizations.includes(i)||i.includes("${");r||(i="");const l=this.getRepositoryList(this.organizationMap,i,n),c=!o||o.includes("${")||l.includes(o);c||(o="");const{tag:g,tags:d}=this.getTags(e.tag,this.repositoryMap,o),m=g===e.tag||s,p={accountOptions:this.newAccounts.sort().map((e=>({label:e,value:e}))),organizationOptions:this.organizations.filter((e=>e)).sort().map((e=>({label:e,value:e}))),imagesLoaded:!0,repositoryOptions:l.sort().map((e=>({label:e,value:e}))),tagOptions:d.sort().map((e=>({label:e,value:e})))};if(!a||this.state.imagesLoaded&&!t||r&&c&&m)a&&a.includes("${")||this.synchronizeChanges(this.state.defineManually?w.splitImageId(a):{organization:i,repository:o,tag:g,digest:this.props.digest},n);else{p.defineManually=!0;const e=[];r||e.push("organization"),c||e.push("image"),m||e.push("tag"),p.missingFields=e,p.switchedManualWarning=`Could not find ${e.join(" or ")}, switched to manual entry`}this.setState(p)}initializeImages(e){if(this.state.imagesLoading)return;const{showRegistry:t,account:a,registry:s}=e,i={provider:"dockerRegistry",account:t?a:s};this.setState({imagesLoading:!0}),L.findImages(i).then((t=>{this.images=t,this.registryMap=this.getRegistryMap(this.images),this.accountMap=this.getAccountMap(this.images),this.newAccounts=this.accounts||Object.keys(this.accountMap),this.organizationMap=this.getOrganizationMap(this.images),this.repositoryMap=this.getRepositoryMap(this.images),this.organizations=this.getOrganizationsList(this.accountMap),this.updateThings(e,!0)})).finally((()=>{this.unmounted||this.setState({imagesLoading:!1})}))}refreshImages(e){this.initializeImages(e)}initializeAccounts(e){let{account:t}=e;a.listAccounts("dockerRegistry").then((a=>{const s=a.map((e=>e.name));this.props.showRegistry&&!t&&(t=s[0]),this.accounts=s,this.refreshImages({...e,account:t})}))}isNew(){const{account:e,organization:t,registry:a,repository:s,tag:i}=this.props;return!(e||t||a||s||i)}componentDidMount(){this.props.deferInitialization||!this.props.registry&&!this.isNew()||this.initializeAccounts(this.props)}valueChanged(e,t){const a={[e]:t};if(_.some((t=>t===e))){const{organization:e,repository:t,tag:s,digest:i}=this.props,n={organization:e,repository:t,tag:s,digest:i,...a},o=w.generateImageId(n);a.imageId=o}this.props.onChange&&this.props.onChange(a)}render(){const{account:e,allowManualDefinition:t,digest:a,imageId:o,organization:r,repository:l,showDigest:c,showRegistry:g,specifyTagByRegex:d,tag:m}=this.props,{accountOptions:p,switchedManualWarning:u,missingFields:h,imagesLoading:f,lookupType:v,organizationOptions:y,repositoryOptions:k,defineManually:N,tagOptions:I}=this.state,C=w.splitImageId(o),x=b.createElement("div",{className:"sp-formItem groupHeader"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Define Image ID"),b.createElement("div",{className:"sp-formActions sp-formActions--mobile"},b.createElement("span",{className:"action"}))),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},b.createElement(E,{value:N,disabled:f||!t,onChange:e=>this.showManualInput(e.value),options:O,clearable:!1}))))),z=u?b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"}),b.createElement("div",{className:"sp-formItem__right"},b.createElement(s,{type:"warning",message:b.createElement(b.Fragment,null,u,(h||[]).map((e=>b.createElement("div",{key:e},b.createElement(i,{expand:!0,id:`pipeline.config.docker.trigger.missing.${e}`})))))}))):null;if(N)return b.createElement("div",{className:"sp-formGroup"},x,b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Image ID"),b.createElement("div",{className:"sp-formActions sp-formActions--mobile"},b.createElement("span",{className:"action"}))),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},b.createElement("input",{className:"form-control input-sm",value:o||"",onChange:e=>this.valueChanged("imageId",e.target.value)}))))),z);const S=g?b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Registry Name")),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},b.createElement(E,{value:e,disabled:f,onChange:e=>this.valueChanged("account",e?e.value:""),options:p,isLoading:f})),b.createElement("span",{className:"sp-formActions sp-formActions--web"},b.createElement("span",{className:"action"},b.createElement(n,{value:f?"Images refreshing":"Refresh images list"},b.createElement("i",{className:"fa icon-button-refresh-arrows "+(f?"fa-spin":""),onClick:this.handleRefreshImages}))))))):null,L=b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Organization")),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},r.includes("${")?b.createElement("input",{disabled:f,className:"form-control input-sm",value:r||"",onChange:e=>this.valueChanged("organization",e.target.value)}):b.createElement(E,{value:r||"",disabled:f,onChange:e=>this.valueChanged("organization",e&&e.value||""),placeholder:"No organization",options:y,isLoading:f}))))),_=b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Image")),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},l.includes("${")?b.createElement("input",{className:"form-control input-sm",disabled:f,value:l||"",onChange:e=>this.valueChanged("repository",e.target.value)}):b.createElement(E,{value:l||"",disabled:f,onChange:e=>this.valueChanged("repository",e&&e.value||""),options:k,required:!0,isLoading:f}))))),T="tag"===v?d?b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Tag")),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},b.createElement("input",{type:"text",className:"form-control input-sm",value:m||"",disabled:f||!l,onChange:e=>this.valueChanged("tag",e.target.value)}))),b.createElement(i,{id:"pipeline.config.docker.trigger.tag",expand:!0}))):b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Tag")),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},m&&m.includes("${")?b.createElement("input",{className:"form-control input-sm",disabled:f,value:m||"",onChange:e=>this.valueChanged("tag",e.target.value),required:!0}):b.createElement(b.Fragment,null,b.createElement(E,{value:m||"",disabled:f||!l,isLoading:f,onChange:e=>this.valueChanged("tag",e?e.value:void 0),options:I,placeholder:"No tag",required:!0}),b.createElement(i,{id:"pipeline.config.docker.trigger.tag.additionalInfo",expand:!0})))))):null,M="digest"===v?b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Digest ",b.createElement(i,{id:"pipeline.config.docker.trigger.digest"}))),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},b.createElement("input",{className:"form-control input-sm",placeholder:"sha256:abc123",value:a||C.digest||"",onChange:e=>this.valueChanged("digest",e.target.value),required:!0}))))):null,R=c?b.createElement("div",{className:"sp-formItem"},b.createElement("div",{className:"sp-formItem__left"},b.createElement("div",{className:"sp-formLabel"},"Type")),b.createElement("div",{className:"sp-formItem__right"},b.createElement("div",{className:"sp-form"},b.createElement("span",{className:"field"},b.createElement(E,{clearable:!1,value:v,options:[{value:"digest",label:"Digest"},{value:"tag",label:"Tag"}],onChange:this.lookupTypeChanged}))))):null;return b.createElement("div",{className:"sp-formGroup"},x,S,L,_,R,M,T)}}T.defaultProps={organization:"",registry:"",repository:"",showDigest:!0,allowManualDefinition:!0};k("spinnaker.docker.pipeline.stage.bake.executionDetails.controller",[N]).controller("dockerBakeExecutionDetailsCtrl",["$scope","$stateParams","executionDetailsSectionService","$interpolate",function(e,t,a,s){e.configSections=["bakeConfig","taskStatus"];const i=()=>{e.detailsSection=t.details,e.provider=e.stage.context.cloudProviderType||"docker",e.bakeryDetailUrl=s(e.roscoMode&&o.roscoDetailUrl?o.roscoDetailUrl:o.bakeryDetailUrl)},n=()=>a.synchronizeSection(e.configSections,i);n(),e.$on("$stateChangeSuccess",n)}]);k("spinnaker.docker.pipeline.stage.bakeStage",["spinnaker.docker.pipeline.stage.bake.executionDetails.controller"]).config((function(){r.pipeline.registerStage({provides:"bake",cloudProvider:"docker",label:"Bake",description:"Bakes an image",templateUrl:"docker/src/pipeline/stages/bake/bakeStage.html",executionDetailsUrl:"docker/src/pipeline/stages/bake/bakeExecutionDetails.html",executionLabelComponent:l,extraLabelLines:e=>e.masterStage.context.allPreviouslyBaked||e.masterStage.context.somePreviouslyBaked?1:0,supportsCustomTimeout:!0,validators:[{type:"requiredField",fieldName:"package"}],restartable:!0})})).controller("dockerBakeStageCtrl",["$scope","$q",function(e,t){e.stage.region="global",e.stage.user||(e.stage.user=c.getAuthenticatedUser().name),e.viewState={loading:!0},e.$watch("stage",(function(){p.forOwn(e.stage,(function(t,a){""===t&&delete e.stage[a]}))}),!0),e.viewState.providerSelected=!0,t.all([g.getBaseOsOptions("docker"),g.getBaseLabelOptions()]).then((function([t,a]){e.baseOsOptions=t.baseImages,e.baseLabelOptions=a,!e.stage.baseOs&&e.baseOsOptions&&e.baseOsOptions.length&&(e.stage.baseOs=e.baseOsOptions[0].id),!e.stage.baseLabel&&e.baseLabelOptions&&e.baseLabelOptions.length&&(e.stage.baseLabel=e.baseLabelOptions[0]),e.viewState.loading=!1}))}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("docker/src/pipeline/stages/bake/bakeStage.html",'<div ng-controller="dockerBakeStageCtrl as bakeStageCtrl">\n <stage-config-field label="Package" help-key="pipeline.config.bake.package">\n <input type="text" class="form-control input-sm" ng-model="stage.package" />\n </stage-config-field>\n <stage-config-field label="Organization" help-key="pipeline.config.docker.bake.organization">\n <input type="text" class="form-control input-sm" ng-model="stage.organization" />\n </stage-config-field>\n <stage-config-field label="Image Name" help-key="pipeline.config.docker.bake.targetImage">\n <input type="text" class="form-control input-sm" ng-model="stage.ami_name" />\n </stage-config-field>\n <stage-config-field label="Image tag" help-key="pipeline.config.docker.bake.targetImageTag">\n <input type="text" class="form-control input-sm" ng-model="stage.extendedAttributes[\'docker_target_image_tag\']" />\n </stage-config-field>\n <stage-config-field label="Base OS">\n <bake-stage-choose-os model="stage.baseOs" base-os-options="baseOsOptions"></bake-stage-choose-os>\n </stage-config-field>\n\n <stage-config-field label="Base Label">\n <label class="radio-inline" ng-repeat="baseLabel in baseLabelOptions">\n <input type="radio" ng-model="stage.baseLabel" ng-value="baseLabel" />\n {{baseLabel}}\n </label>\n </stage-config-field>\n <stage-config-field label="Rebake">\n <div class="checkbox" style="margin-bottom: 0">\n <label>\n <input type="checkbox" ng-model="stage.rebake" />\n Rebake image without regard to the status of any existing bake\n </label>\n </div>\n </stage-config-field>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("docker/src/pipeline/stages/bake/bakeExecutionDetails.html",'<div ng-controller="dockerBakeExecutionDetailsCtrl">\n <execution-details-section-nav sections="configSections"></execution-details-section-nav>\n <div class="step-section-details" ng-if="detailsSection === \'bakeConfig\'">\n <div class="row">\n <div class="col-md-6">\n <dl class="dl-narrow dl-horizontal">\n <dt if-multiple-providers>Provider</dt>\n <dd if-multiple-providers>Docker</dd>\n <dt>Organization</dt>\n <dd>{{stage.context.organization}}</dd>\n <dt>Image Name</dt>\n <dd>{{stage.context.ami_name}}</dd>\n <dt>Image Tag</dt>\n <dd>{{stage.context.extendedAttributes[\'docker_target_image_tag\']}}</dd>\n <dt>Image</dt>\n <dd>{{stage.context.ami}}</dd>\n </dl>\n </div>\n <div class="col-md-6">\n <dl class="dl-narrow dl-horizontal">\n <dt>Base OS</dt>\n <dd>{{stage.context.baseOs}}</dd>\n <dt>Region</dt>\n <dd>{{stage.context.region}}</dd>\n <dt>Package</dt>\n <dd>{{stage.context.package}}</dd>\n <dt>Label</dt>\n <dd>{{stage.context.baseLabel}}</dd>\n </dl>\n </div>\n </div>\n <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>\n\n <div class="row" ng-if="stage.context.region && stage.context.status.resourceId">\n <div class="col-md-12">\n <div class="alert alert-{{stage.isFailed ? \'danger\' : \'info\'}}">\n <a target="_blank" href="{{ bakeryDetailUrl(stage) }}"> View Bakery Details </a>\n </div>\n </div>\n </div>\n </div>\n <div class="step-section-details" ng-if="detailsSection === \'taskStatus\'">\n <div class="row">\n <execution-step-details item="stage"></execution-step-details>\n </div>\n </div>\n</div>\n')}]);const M=[{value:"digest",label:"Digest"},{value:"tag",label:"Tag"}];class R extends b.Component{constructor(e){super(e),this.queryStream=new C,this.handleQuery=()=>{const e=this.props.command.trigger;return x(L.findTags({provider:"dockerRegistry",account:e.account,repository:e.repository}))},this.lookupTypeChanged=e=>{const t=e.value;this.updateArtifact(this.props.command,"tag"===t?this.state.selectedTag:this.state.digest),this.setState({lookupType:t})},this.updateSelectedTag=e=>{this.updateArtifact(this.props.command,e),this.setState({selectedTag:e}),this.props.command.triggerInvalid=!1},this.updateDigest=e=>{this.updateArtifact(this.props.command,e),this.setState({digest:e})},this.tagLoadSuccess=e=>{const{command:t}=this.props,a=t.trigger,s={};s.tags=e||[];const i=s.tags.find((e=>e===a.tag));i&&(s.selectedTag=i,this.updateSelectedTag(i)),s.tagsLoading=!1,this.setState(s)},this.tagLoadFailure=()=>{this.setState({tagsLoading:!1,loadError:!0})},this.initialize=()=>{const{command:e}=this.props;this.props.updateCommand("triggerInvalid",!0),this.props.updateCommand("extraFields",{tag:y(e,"extraFields.tag",""),artifacts:y(e,"extraFields.artifacts","")}),this.subscription&&this.subscription.unsubscribe(),"docker"===e.trigger.type&&(this.subscription=this.queryStream.pipe(z(250),S(this.handleQuery)).subscribe(this.tagLoadSuccess,this.tagLoadFailure),this.searchTags())},this.searchTags=(e="")=>{this.setState({tags:[`<span>Finding tags${e&&` matching ${e}`}...</span>`]}),this.queryStream.next()},this.state={digest:"",tags:[],tagsLoading:!0,loadError:!1,lookupType:"tag",selectedTag:""}}static formatLabel(e){return I.when(`(Docker Registry) ${e.account?e.account+":":""} ${e.repository||""}`)}updateArtifact(e,t){this.props.updateCommand("extraFields.tag",t);const a=e.trigger;if(a&&a.repository){let e="";a.registry&&(e+=a.registry+"/"),e+=a.repository;let s="";s="digest"===this.state.lookupType?`${e}@${t}`:`${e}:${t}`,this.props.updateCommand("extraFields.artifacts",[{type:"docker/image",name:e,version:t,reference:s}])}}componentDidMount(){this.initialize()}componentWillUnmount(){this.subscription&&this.subscription.unsubscribe()}render(){const{digest:e,tags:t,tagsLoading:a,loadError:s,selectedTag:n,lookupType:o}=this.state,r=t.map((e=>({value:e})));return b.createElement(b.Fragment,null,b.createElement("div",{className:"form-group"},b.createElement("div",{className:"sm-label-right col-md-4"},"Type"),b.createElement("div",{className:"col-md-3"},b.createElement(d,{clearable:!1,value:o,options:M,onChange:this.lookupTypeChanged}))),"tag"===o&&b.createElement("div",{className:"form-group"},b.createElement("label",{className:"col-md-4 sm-label-right"},"Tag"),a&&b.createElement("div",{className:"col-md-6"},b.createElement("div",{className:"form-control-static text-center"},b.createElement(m,{size:"small"}))),s&&b.createElement("div",{className:"col-md-6"},"Error loading tags!"),!a&&b.createElement("div",{className:"col-md-6"},0===t.length&&b.createElement("div",null,b.createElement("p",{className:"form-control-static"},"No tags found")),t.length>0&&b.createElement(d,{options:r,optionRenderer:e=>b.createElement("span",null,e.value),clearable:!1,value:n,valueRenderer:e=>b.createElement("span",null,b.createElement("strong",null,e.value)),onChange:e=>this.updateSelectedTag(e.value),placeholder:"Search tags..."}))),"digest"===o&&b.createElement("div",{className:"form-group"},b.createElement("label",{className:"col-md-4 sm-label-right"},"Digest ",b.createElement(i,{id:"pipeline.config.docker.trigger.digest"})),b.createElement("div",{className:"col-md-6"},b.createElement("input",{value:e,onChange:e=>this.updateDigest(e.target.value),className:"form-control input-sm",required:!0}))))}}r.pipeline.registerTrigger({label:"Docker Registry",description:"Executes the pipeline on an image update",key:"docker",component:function(e){const{formik:t}=e,a=t.values;return b.createElement("div",{className:"form-horizontal"},b.createElement(T,{allowManualDefinition:!1,specifyTagByRegex:!0,account:a.account,organization:a.organization,registry:a.registry,repository:a.repository,tag:a.tag,showRegistry:!0,onChange:t=>{const{imageId:a,...s}=t;e.triggerUpdated(s)},showDigest:!1}))},manualExecutionComponent:R,executionStatusComponent:e=>{const t=e.trigger;return b.createElement("li",null,t.repository,":",t.tag)},executionTriggerLabel:()=>"Docker Registry",validators:[{type:"requiredField",fieldName:"account",message:"<strong>Registry</strong> is a required field for Docker Registry triggers."},{type:"requiredField",fieldName:"repository",message:"<strong>Image</strong> is a required field for Docker Registry triggers."},{type:"serviceAccountAccess",preventSave:!0,message:"You do not have access to the service account configured in this pipeline's Docker Registry trigger.\n You will not be able to save your edits to this pipeline."}]});const D="spinnaker.docker";k("spinnaker.docker",["spinnaker.docker.pipeline.stage.bakeStage"]);export{D as DOCKER_MODULE,T as DockerImageAndTagSelector,L as DockerImageReader,w as DockerImageUtils};
1
+ import { RetryService, REST, AccountService, ValidationMessage, HelpField, Tooltip, SETTINGS, Registry, BakeExecutionLabel, AuthenticationService, BakeryReader, TetheredSelect, Spinner } from '@spinnaker/core';
2
+ import _, { groupBy, reduce, uniq, trim, get } from 'lodash';
3
+ import React from 'react';
4
+ import Select from 'react-select';
5
+ import { module } from 'angular';
6
+ import UIROUTER_ANGULARJS from '@uirouter/angularjs';
7
+ import { $q } from 'ngimport';
8
+ import { Subject, from } from 'rxjs';
9
+ import { debounceTime, switchMap } from 'rxjs/operators';
10
+
11
+ class DockerImageReader {
12
+ static getImage(imageName, region, credentials) {
13
+ return REST("/images").path(credentials, region, imageName).query({ provider: "docker" }).get().then((results) => results && results.length ? results[0] : null).catch(() => null);
14
+ }
15
+ static findImages(params) {
16
+ return RetryService.buildRetrySequence(() => REST("/images/find").query(params).get(), (results) => results.length > 0, 10, 1e3).then((results) => results).catch(() => []);
17
+ }
18
+ static findTags(params) {
19
+ return RetryService.buildRetrySequence(() => REST("/images/tags").query(params).get(), (results) => results.length > 0, 10, 1e3).then((results) => results).catch(() => []);
20
+ }
21
+ }
22
+
23
+ class DockerImageUtils {
24
+ static splitImageId(imageId = "") {
25
+ let imageParts;
26
+ if (imageId.includes("@")) {
27
+ imageParts = imageId.split("@");
28
+ } else {
29
+ imageParts = imageId.split(":");
30
+ }
31
+ const repository = imageParts[0];
32
+ const repositoryParts = repository.split("/");
33
+ const organization = repositoryParts.slice(0, -1).join("/");
34
+ const lookup = imageParts.length > 1 ? imageParts.slice(1).join(":") : "";
35
+ let tag;
36
+ let digest;
37
+ if (lookup) {
38
+ if (lookup.startsWith("sha256:")) {
39
+ digest = lookup;
40
+ } else {
41
+ tag = lookup;
42
+ }
43
+ }
44
+ return { organization, repository, digest, tag };
45
+ }
46
+ static generateImageId(parts) {
47
+ if (!parts.repository || !(parts.digest || parts.tag)) {
48
+ return void 0;
49
+ }
50
+ let imageId;
51
+ if (parts.digest) {
52
+ imageId = `${parts.repository}@${parts.digest}`;
53
+ } else {
54
+ imageId = `${parts.repository}:${parts.tag}`;
55
+ }
56
+ return imageId;
57
+ }
58
+ }
59
+
60
+ const imageFields = ["organization", "repository", "tag", "digest"];
61
+ const defineOptions = [
62
+ { label: "Manually", value: true },
63
+ { label: "Select from list", value: false }
64
+ ];
65
+ class DockerImageAndTagSelector extends React.Component {
66
+ constructor(props) {
67
+ super(props);
68
+ this.unmounted = false;
69
+ this.cachedValues = {};
70
+ this.handleRefreshImages = () => {
71
+ this.refreshImages(this.props);
72
+ };
73
+ this.lookupTypeChanged = (o) => {
74
+ const newType = o.value;
75
+ const oldType = this.state.lookupType;
76
+ const oldValue = this.props[oldType];
77
+ const cachedValue = this.cachedValues[newType];
78
+ this.valueChanged(oldType, void 0);
79
+ if (this.cachedValues[newType]) {
80
+ this.valueChanged(newType, cachedValue);
81
+ }
82
+ this.setState({ lookupType: newType });
83
+ this.cachedValues[oldType] = oldValue;
84
+ };
85
+ this.showManualInput = (defineManually) => {
86
+ if (!defineManually) {
87
+ const newFields = DockerImageUtils.splitImageId(this.props.imageId || "");
88
+ this.props.onChange(newFields);
89
+ if (this.state.switchedManualWarning) {
90
+ this.setState({ switchedManualWarning: void 0, missingFields: void 0 });
91
+ }
92
+ }
93
+ this.setState({ defineManually });
94
+ };
95
+ const accountOptions = props.account ? [{ label: props.account, value: props.account }] : [];
96
+ const organizationOptions = props.organization && props.organization.length ? [{ label: props.organization, value: props.organization }] : [];
97
+ const repositoryOptions = props.repository && props.repository.length ? [{ label: props.repository, value: props.repository }] : [];
98
+ const tagOptions = props.tag && props.tag.length ? [{ label: props.tag, value: props.tag }] : [];
99
+ const parsedImageId = DockerImageUtils.splitImageId(props.imageId);
100
+ const defineManually = props.allowManualDefinition && Boolean(props.imageId && props.imageId.includes("${"));
101
+ this.state = {
102
+ accountOptions,
103
+ switchedManualWarning: void 0,
104
+ imagesLoaded: false,
105
+ imagesLoading: false,
106
+ organizationOptions,
107
+ repositoryOptions,
108
+ defineManually,
109
+ tagOptions,
110
+ lookupType: props.digest || parsedImageId.digest ? "digest" : "tag"
111
+ };
112
+ }
113
+ getAccountMap(images) {
114
+ const groupedImages = groupBy(images.filter((image) => image.account), "account");
115
+ return reduce(groupedImages, (acc, image, key) => {
116
+ acc[key] = uniq(image.map((i) => `${i.repository.split("/").slice(0, -1).join("/")}`));
117
+ return acc;
118
+ }, {});
119
+ }
120
+ getRegistryMap(images) {
121
+ return images.reduce((m, image) => {
122
+ m[image.account] = image.registry;
123
+ return m;
124
+ }, {});
125
+ }
126
+ getOrganizationMap(images) {
127
+ const extractGroupByKey = (image) => `${image.account}/${image.repository.split("/").slice(0, -1).join("/")}`;
128
+ const groupedImages = groupBy(images.filter((image) => image.repository), extractGroupByKey);
129
+ return reduce(groupedImages, (acc, image, key) => {
130
+ acc[key] = uniq(image.map((i) => i.repository));
131
+ return acc;
132
+ }, {});
133
+ }
134
+ getRepositoryMap(images) {
135
+ const groupedImages = groupBy(images.filter((image) => image.account), "repository");
136
+ return reduce(groupedImages, (acc, image, key) => {
137
+ acc[key] = uniq(image.map((i) => i.tag));
138
+ return acc;
139
+ }, {});
140
+ }
141
+ getOrganizationsList(accountMap) {
142
+ return accountMap ? accountMap[this.props.showRegistry ? this.props.account : this.props.registry] || [] : [];
143
+ }
144
+ getRepositoryList(organizationMap, organization, registry) {
145
+ if (organizationMap) {
146
+ const key = `${this.props.showRegistry ? this.props.account : registry}/${organization || ""}`;
147
+ return organizationMap[key] || [];
148
+ }
149
+ return [];
150
+ }
151
+ getTags(tag, repositoryMap, repository) {
152
+ let tags = [];
153
+ if (this.props.specifyTagByRegex) {
154
+ if (tag && trim(tag) === "") {
155
+ tag = void 0;
156
+ }
157
+ } else {
158
+ if (repositoryMap) {
159
+ tags = repositoryMap[repository] || [];
160
+ if (!tags.includes(tag) && tag && !tag.includes("${")) {
161
+ tag = void 0;
162
+ }
163
+ }
164
+ }
165
+ return { tag, tags };
166
+ }
167
+ componentWillReceiveProps(nextProps) {
168
+ if (!this.images || ["account", "showRegistry"].some((key) => this.props[key] !== nextProps[key])) {
169
+ this.refreshImages(nextProps);
170
+ } else if (["organization", "registry", "repository"].some((key) => this.props[key] !== nextProps[key])) {
171
+ this.updateThings(nextProps);
172
+ }
173
+ if (nextProps.imageId && nextProps.imageId.includes("${")) {
174
+ this.setState({ defineManually: true });
175
+ }
176
+ }
177
+ componentWillUnmount() {
178
+ this.unmounted = true;
179
+ }
180
+ synchronizeChanges(values, registry) {
181
+ const { organization, repository, tag, digest } = values;
182
+ if (this.props.onChange) {
183
+ const imageId = DockerImageUtils.generateImageId({ organization, repository, tag, digest });
184
+ const changes = {};
185
+ if (tag !== this.props.tag) {
186
+ changes.tag = tag;
187
+ }
188
+ if (imageId !== this.props.imageId) {
189
+ changes.imageId = imageId;
190
+ }
191
+ if (organization !== this.props.organization) {
192
+ changes.organization = organization;
193
+ }
194
+ if (registry !== this.props.registry) {
195
+ changes.registry = registry;
196
+ }
197
+ if (repository !== this.props.repository) {
198
+ changes.repository = repository;
199
+ }
200
+ if (digest !== this.props.digest) {
201
+ changes.digest = digest;
202
+ }
203
+ if (Object.keys(changes).length > 0) {
204
+ this.props.onChange(changes);
205
+ }
206
+ }
207
+ }
208
+ updateThings(props, allowAutoSwitchToManualEntry = false) {
209
+ if (!this.repositoryMap || this.unmounted) {
210
+ return;
211
+ }
212
+ const { imageId, specifyTagByRegex } = props;
213
+ let { organization, registry, repository } = props;
214
+ if (props.showRegistry) {
215
+ registry = this.registryMap[props.account];
216
+ }
217
+ const organizationFound = !organization || this.organizations.includes(organization) || organization.includes("${");
218
+ if (!organizationFound) {
219
+ organization = "";
220
+ }
221
+ const repositories = this.getRepositoryList(this.organizationMap, organization, registry);
222
+ const repositoryFound = !repository || repository.includes("${") || repositories.includes(repository);
223
+ if (!repositoryFound) {
224
+ repository = "";
225
+ }
226
+ const { tag, tags } = this.getTags(props.tag, this.repositoryMap, repository);
227
+ const tagFound = tag === props.tag || specifyTagByRegex;
228
+ const newState = {
229
+ accountOptions: this.newAccounts.sort().map((a) => ({ label: a, value: a })),
230
+ organizationOptions: this.organizations.filter((o) => o).sort().map((o) => ({ label: o, value: o })),
231
+ imagesLoaded: true,
232
+ repositoryOptions: repositories.sort().map((r) => ({ label: r, value: r })),
233
+ tagOptions: tags.sort().map((t) => ({ label: t, value: t }))
234
+ };
235
+ if (imageId && (!this.state.imagesLoaded || allowAutoSwitchToManualEntry) && (!organizationFound || !repositoryFound || !tagFound)) {
236
+ newState.defineManually = true;
237
+ const missingFields = [];
238
+ if (!organizationFound) {
239
+ missingFields.push("organization");
240
+ }
241
+ if (!repositoryFound) {
242
+ missingFields.push("image");
243
+ }
244
+ if (!tagFound) {
245
+ missingFields.push("tag");
246
+ }
247
+ newState.missingFields = missingFields;
248
+ newState.switchedManualWarning = `Could not find ${missingFields.join(" or ")}, switched to manual entry`;
249
+ } else if (!imageId || !imageId.includes("${")) {
250
+ this.synchronizeChanges(this.state.defineManually ? DockerImageUtils.splitImageId(imageId) : { organization, repository, tag, digest: this.props.digest }, registry);
251
+ }
252
+ this.setState(newState);
253
+ }
254
+ initializeImages(props) {
255
+ if (this.state.imagesLoading) {
256
+ return;
257
+ }
258
+ const { showRegistry, account, registry } = props;
259
+ const imageConfig = {
260
+ provider: "dockerRegistry",
261
+ account: showRegistry ? account : registry
262
+ };
263
+ this.setState({ imagesLoading: true });
264
+ DockerImageReader.findImages(imageConfig).then((images) => {
265
+ this.images = images;
266
+ this.registryMap = this.getRegistryMap(this.images);
267
+ this.accountMap = this.getAccountMap(this.images);
268
+ this.newAccounts = this.accounts || Object.keys(this.accountMap);
269
+ this.organizationMap = this.getOrganizationMap(this.images);
270
+ this.repositoryMap = this.getRepositoryMap(this.images);
271
+ this.organizations = this.getOrganizationsList(this.accountMap);
272
+ this.updateThings(props, true);
273
+ }).finally(() => {
274
+ if (!this.unmounted) {
275
+ this.setState({ imagesLoading: false });
276
+ }
277
+ });
278
+ }
279
+ refreshImages(props) {
280
+ this.initializeImages(props);
281
+ }
282
+ initializeAccounts(props) {
283
+ let { account } = props;
284
+ AccountService.listAccounts("dockerRegistry").then((allAccounts) => {
285
+ const accounts = allAccounts.map((a) => a.name);
286
+ if (this.props.showRegistry && !account) {
287
+ account = accounts[0];
288
+ }
289
+ this.accounts = accounts;
290
+ this.refreshImages({ ...props, ...{ account } });
291
+ });
292
+ }
293
+ isNew() {
294
+ const { account, organization, registry, repository, tag } = this.props;
295
+ return !account && !organization && !registry && !repository && !tag;
296
+ }
297
+ componentDidMount() {
298
+ if (!this.props.deferInitialization && (this.props.registry || this.isNew())) {
299
+ this.initializeAccounts(this.props);
300
+ }
301
+ }
302
+ valueChanged(name, value) {
303
+ const changes = { [name]: value };
304
+ if (imageFields.some((n) => n === name)) {
305
+ const { organization, repository, tag, digest } = this.props;
306
+ const imageParts = { ...{ organization, repository, tag, digest }, ...changes };
307
+ const imageId = DockerImageUtils.generateImageId(imageParts);
308
+ changes.imageId = imageId;
309
+ }
310
+ this.props.onChange && this.props.onChange(changes);
311
+ }
312
+ render() {
313
+ const {
314
+ account,
315
+ allowManualDefinition,
316
+ digest,
317
+ imageId,
318
+ organization,
319
+ repository,
320
+ showDigest,
321
+ showRegistry,
322
+ specifyTagByRegex,
323
+ tag
324
+ } = this.props;
325
+ const {
326
+ accountOptions,
327
+ switchedManualWarning,
328
+ missingFields,
329
+ imagesLoading,
330
+ lookupType,
331
+ organizationOptions,
332
+ repositoryOptions,
333
+ defineManually,
334
+ tagOptions
335
+ } = this.state;
336
+ const parsedImageId = DockerImageUtils.splitImageId(imageId);
337
+ const manualInputToggle = /* @__PURE__ */ React.createElement("div", {
338
+ className: "sp-formItem groupHeader"
339
+ }, /* @__PURE__ */ React.createElement("div", {
340
+ className: "sp-formItem__left"
341
+ }, /* @__PURE__ */ React.createElement("div", {
342
+ className: "sp-formLabel"
343
+ }, "Define Image ID"), /* @__PURE__ */ React.createElement("div", {
344
+ className: "sp-formActions sp-formActions--mobile"
345
+ }, /* @__PURE__ */ React.createElement("span", {
346
+ className: "action"
347
+ }))), /* @__PURE__ */ React.createElement("div", {
348
+ className: "sp-formItem__right"
349
+ }, /* @__PURE__ */ React.createElement("div", {
350
+ className: "sp-form"
351
+ }, /* @__PURE__ */ React.createElement("span", {
352
+ className: "field"
353
+ }, /* @__PURE__ */ React.createElement(Select, {
354
+ value: defineManually,
355
+ disabled: imagesLoading || !allowManualDefinition,
356
+ onChange: (o) => this.showManualInput(o.value),
357
+ options: defineOptions,
358
+ clearable: false
359
+ })))));
360
+ const warning = switchedManualWarning ? /* @__PURE__ */ React.createElement("div", {
361
+ className: "sp-formItem"
362
+ }, /* @__PURE__ */ React.createElement("div", {
363
+ className: "sp-formItem__left"
364
+ }), /* @__PURE__ */ React.createElement("div", {
365
+ className: "sp-formItem__right"
366
+ }, /* @__PURE__ */ React.createElement(ValidationMessage, {
367
+ type: "warning",
368
+ message: /* @__PURE__ */ React.createElement(React.Fragment, null, switchedManualWarning, (missingFields || []).map((f) => /* @__PURE__ */ React.createElement("div", {
369
+ key: f
370
+ }, /* @__PURE__ */ React.createElement(HelpField, {
371
+ expand: true,
372
+ id: `pipeline.config.docker.trigger.missing.${f}`
373
+ }))))
374
+ }))) : null;
375
+ if (defineManually) {
376
+ return /* @__PURE__ */ React.createElement("div", {
377
+ className: "sp-formGroup"
378
+ }, manualInputToggle, /* @__PURE__ */ React.createElement("div", {
379
+ className: "sp-formItem"
380
+ }, /* @__PURE__ */ React.createElement("div", {
381
+ className: "sp-formItem__left"
382
+ }, /* @__PURE__ */ React.createElement("div", {
383
+ className: "sp-formLabel"
384
+ }, "Image ID"), /* @__PURE__ */ React.createElement("div", {
385
+ className: "sp-formActions sp-formActions--mobile"
386
+ }, /* @__PURE__ */ React.createElement("span", {
387
+ className: "action"
388
+ }))), /* @__PURE__ */ React.createElement("div", {
389
+ className: "sp-formItem__right"
390
+ }, /* @__PURE__ */ React.createElement("div", {
391
+ className: "sp-form"
392
+ }, /* @__PURE__ */ React.createElement("span", {
393
+ className: "field"
394
+ }, /* @__PURE__ */ React.createElement("input", {
395
+ className: "form-control input-sm",
396
+ value: imageId || "",
397
+ onChange: (e) => this.valueChanged("imageId", e.target.value)
398
+ }))))), warning);
399
+ }
400
+ const Registry = showRegistry ? /* @__PURE__ */ React.createElement("div", {
401
+ className: "sp-formItem"
402
+ }, /* @__PURE__ */ React.createElement("div", {
403
+ className: "sp-formItem__left"
404
+ }, /* @__PURE__ */ React.createElement("div", {
405
+ className: "sp-formLabel"
406
+ }, "Registry Name")), /* @__PURE__ */ React.createElement("div", {
407
+ className: "sp-formItem__right"
408
+ }, /* @__PURE__ */ React.createElement("div", {
409
+ className: "sp-form"
410
+ }, /* @__PURE__ */ React.createElement("span", {
411
+ className: "field"
412
+ }, /* @__PURE__ */ React.createElement(Select, {
413
+ value: account,
414
+ disabled: imagesLoading,
415
+ onChange: (o) => this.valueChanged("account", o ? o.value : ""),
416
+ options: accountOptions,
417
+ isLoading: imagesLoading
418
+ })), /* @__PURE__ */ React.createElement("span", {
419
+ className: "sp-formActions sp-formActions--web"
420
+ }, /* @__PURE__ */ React.createElement("span", {
421
+ className: "action"
422
+ }, /* @__PURE__ */ React.createElement(Tooltip, {
423
+ value: imagesLoading ? "Images refreshing" : "Refresh images list"
424
+ }, /* @__PURE__ */ React.createElement("i", {
425
+ className: `fa icon-button-refresh-arrows ${imagesLoading ? "fa-spin" : ""}`,
426
+ onClick: this.handleRefreshImages
427
+ }))))))) : null;
428
+ const Organization = /* @__PURE__ */ React.createElement("div", {
429
+ className: "sp-formItem"
430
+ }, /* @__PURE__ */ React.createElement("div", {
431
+ className: "sp-formItem__left"
432
+ }, /* @__PURE__ */ React.createElement("div", {
433
+ className: "sp-formLabel"
434
+ }, "Organization")), /* @__PURE__ */ React.createElement("div", {
435
+ className: "sp-formItem__right"
436
+ }, /* @__PURE__ */ React.createElement("div", {
437
+ className: "sp-form"
438
+ }, /* @__PURE__ */ React.createElement("span", {
439
+ className: "field"
440
+ }, organization.includes("${") ? /* @__PURE__ */ React.createElement("input", {
441
+ disabled: imagesLoading,
442
+ className: "form-control input-sm",
443
+ value: organization || "",
444
+ onChange: (e) => this.valueChanged("organization", e.target.value)
445
+ }) : /* @__PURE__ */ React.createElement(Select, {
446
+ value: organization || "",
447
+ disabled: imagesLoading,
448
+ onChange: (o) => this.valueChanged("organization", o && o.value || ""),
449
+ placeholder: "No organization",
450
+ options: organizationOptions,
451
+ isLoading: imagesLoading
452
+ })))));
453
+ const Image = /* @__PURE__ */ React.createElement("div", {
454
+ className: "sp-formItem"
455
+ }, /* @__PURE__ */ React.createElement("div", {
456
+ className: "sp-formItem__left"
457
+ }, /* @__PURE__ */ React.createElement("div", {
458
+ className: "sp-formLabel"
459
+ }, "Image")), /* @__PURE__ */ React.createElement("div", {
460
+ className: "sp-formItem__right"
461
+ }, /* @__PURE__ */ React.createElement("div", {
462
+ className: "sp-form"
463
+ }, /* @__PURE__ */ React.createElement("span", {
464
+ className: "field"
465
+ }, repository.includes("${") ? /* @__PURE__ */ React.createElement("input", {
466
+ className: "form-control input-sm",
467
+ disabled: imagesLoading,
468
+ value: repository || "",
469
+ onChange: (e) => this.valueChanged("repository", e.target.value)
470
+ }) : /* @__PURE__ */ React.createElement(Select, {
471
+ value: repository || "",
472
+ disabled: imagesLoading,
473
+ onChange: (o) => this.valueChanged("repository", o && o.value || ""),
474
+ options: repositoryOptions,
475
+ required: true,
476
+ isLoading: imagesLoading
477
+ })))));
478
+ const Tag = lookupType === "tag" ? specifyTagByRegex ? /* @__PURE__ */ React.createElement("div", {
479
+ className: "sp-formItem"
480
+ }, /* @__PURE__ */ React.createElement("div", {
481
+ className: "sp-formItem__left"
482
+ }, /* @__PURE__ */ React.createElement("div", {
483
+ className: "sp-formLabel"
484
+ }, "Tag")), /* @__PURE__ */ React.createElement("div", {
485
+ className: "sp-formItem__right"
486
+ }, /* @__PURE__ */ React.createElement("div", {
487
+ className: "sp-form"
488
+ }, /* @__PURE__ */ React.createElement("span", {
489
+ className: "field"
490
+ }, /* @__PURE__ */ React.createElement("input", {
491
+ type: "text",
492
+ className: "form-control input-sm",
493
+ value: tag || "",
494
+ disabled: imagesLoading || !repository,
495
+ onChange: (e) => this.valueChanged("tag", e.target.value)
496
+ }))), /* @__PURE__ */ React.createElement(HelpField, {
497
+ id: "pipeline.config.docker.trigger.tag",
498
+ expand: true
499
+ }))) : /* @__PURE__ */ React.createElement("div", {
500
+ className: "sp-formItem"
501
+ }, /* @__PURE__ */ React.createElement("div", {
502
+ className: "sp-formItem__left"
503
+ }, /* @__PURE__ */ React.createElement("div", {
504
+ className: "sp-formLabel"
505
+ }, "Tag")), /* @__PURE__ */ React.createElement("div", {
506
+ className: "sp-formItem__right"
507
+ }, /* @__PURE__ */ React.createElement("div", {
508
+ className: "sp-form"
509
+ }, /* @__PURE__ */ React.createElement("span", {
510
+ className: "field"
511
+ }, tag && tag.includes("${") ? /* @__PURE__ */ React.createElement("input", {
512
+ className: "form-control input-sm",
513
+ disabled: imagesLoading,
514
+ value: tag || "",
515
+ onChange: (e) => this.valueChanged("tag", e.target.value),
516
+ required: true
517
+ }) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Select, {
518
+ value: tag || "",
519
+ disabled: imagesLoading || !repository,
520
+ isLoading: imagesLoading,
521
+ onChange: (o) => this.valueChanged("tag", o ? o.value : void 0),
522
+ options: tagOptions,
523
+ placeholder: "No tag",
524
+ required: true
525
+ }), /* @__PURE__ */ React.createElement(HelpField, {
526
+ id: "pipeline.config.docker.trigger.tag.additionalInfo",
527
+ expand: true
528
+ })))))) : null;
529
+ const Digest = lookupType === "digest" ? /* @__PURE__ */ React.createElement("div", {
530
+ className: "sp-formItem"
531
+ }, /* @__PURE__ */ React.createElement("div", {
532
+ className: "sp-formItem__left"
533
+ }, /* @__PURE__ */ React.createElement("div", {
534
+ className: "sp-formLabel"
535
+ }, "Digest ", /* @__PURE__ */ React.createElement(HelpField, {
536
+ id: "pipeline.config.docker.trigger.digest"
537
+ }))), /* @__PURE__ */ React.createElement("div", {
538
+ className: "sp-formItem__right"
539
+ }, /* @__PURE__ */ React.createElement("div", {
540
+ className: "sp-form"
541
+ }, /* @__PURE__ */ React.createElement("span", {
542
+ className: "field"
543
+ }, /* @__PURE__ */ React.createElement("input", {
544
+ className: "form-control input-sm",
545
+ placeholder: "sha256:abc123",
546
+ value: digest || parsedImageId.digest || "",
547
+ onChange: (e) => this.valueChanged("digest", e.target.value),
548
+ required: true
549
+ }))))) : null;
550
+ const LookupTypeSelector = showDigest ? /* @__PURE__ */ React.createElement("div", {
551
+ className: "sp-formItem"
552
+ }, /* @__PURE__ */ React.createElement("div", {
553
+ className: "sp-formItem__left"
554
+ }, /* @__PURE__ */ React.createElement("div", {
555
+ className: "sp-formLabel"
556
+ }, "Type")), /* @__PURE__ */ React.createElement("div", {
557
+ className: "sp-formItem__right"
558
+ }, /* @__PURE__ */ React.createElement("div", {
559
+ className: "sp-form"
560
+ }, /* @__PURE__ */ React.createElement("span", {
561
+ className: "field"
562
+ }, /* @__PURE__ */ React.createElement(Select, {
563
+ clearable: false,
564
+ value: lookupType,
565
+ options: [
566
+ { value: "digest", label: "Digest" },
567
+ { value: "tag", label: "Tag" }
568
+ ],
569
+ onChange: this.lookupTypeChanged
570
+ }))))) : null;
571
+ return /* @__PURE__ */ React.createElement("div", {
572
+ className: "sp-formGroup"
573
+ }, manualInputToggle, Registry, Organization, Image, LookupTypeSelector, Digest, Tag);
574
+ }
575
+ }
576
+ DockerImageAndTagSelector.defaultProps = {
577
+ organization: "",
578
+ registry: "",
579
+ repository: "",
580
+ showDigest: true,
581
+ allowManualDefinition: true
582
+ };
583
+
584
+ const DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER = "spinnaker.docker.pipeline.stage.bake.executionDetails.controller";
585
+ module(DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER, [UIROUTER_ANGULARJS]).controller("dockerBakeExecutionDetailsCtrl", [
586
+ "$scope",
587
+ "$stateParams",
588
+ "executionDetailsSectionService",
589
+ "$interpolate",
590
+ function($scope, $stateParams, executionDetailsSectionService, $interpolate) {
591
+ $scope.configSections = ["bakeConfig", "taskStatus"];
592
+ const initialized = () => {
593
+ $scope.detailsSection = $stateParams.details;
594
+ $scope.provider = $scope.stage.context.cloudProviderType || "docker";
595
+ $scope.bakeryDetailUrl = $interpolate($scope.roscoMode && SETTINGS.roscoDetailUrl ? SETTINGS.roscoDetailUrl : SETTINGS.bakeryDetailUrl);
596
+ };
597
+ const initialize = () => executionDetailsSectionService.synchronizeSection($scope.configSections, initialized);
598
+ initialize();
599
+ $scope.$on("$stateChangeSuccess", initialize);
600
+ }
601
+ ]);
602
+
603
+ const DOCKER_PIPELINE_STAGES_BAKE_DOCKERBAKESTAGE = "spinnaker.docker.pipeline.stage.bakeStage";
604
+ module(DOCKER_PIPELINE_STAGES_BAKE_DOCKERBAKESTAGE, [DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER]).config(function() {
605
+ Registry.pipeline.registerStage({
606
+ provides: "bake",
607
+ cloudProvider: "docker",
608
+ label: "Bake",
609
+ description: "Bakes an image",
610
+ templateUrl: "docker/src/pipeline/stages/bake/bakeStage.html",
611
+ executionDetailsUrl: "docker/src/pipeline/stages/bake/bakeExecutionDetails.html",
612
+ executionLabelComponent: BakeExecutionLabel,
613
+ extraLabelLines: (stage) => {
614
+ return stage.masterStage.context.allPreviouslyBaked || stage.masterStage.context.somePreviouslyBaked ? 1 : 0;
615
+ },
616
+ supportsCustomTimeout: true,
617
+ validators: [{ type: "requiredField", fieldName: "package" }],
618
+ restartable: true
619
+ });
620
+ }).controller("dockerBakeStageCtrl", [
621
+ "$scope",
622
+ "$q",
623
+ function($scope, $q) {
624
+ const stage = $scope.stage;
625
+ stage.region = "global";
626
+ if (!$scope.stage.user) {
627
+ $scope.stage.user = AuthenticationService.getAuthenticatedUser().name;
628
+ }
629
+ $scope.viewState = {
630
+ loading: true
631
+ };
632
+ function initialize() {
633
+ $scope.viewState.providerSelected = true;
634
+ $q.all([BakeryReader.getBaseOsOptions("docker"), BakeryReader.getBaseLabelOptions()]).then(function([
635
+ baseOsOptions,
636
+ baseLabelOptions
637
+ ]) {
638
+ $scope.baseOsOptions = baseOsOptions.baseImages;
639
+ $scope.baseLabelOptions = baseLabelOptions;
640
+ if (!$scope.stage.baseOs && $scope.baseOsOptions && $scope.baseOsOptions.length) {
641
+ $scope.stage.baseOs = $scope.baseOsOptions[0].id;
642
+ }
643
+ if (!$scope.stage.baseLabel && $scope.baseLabelOptions && $scope.baseLabelOptions.length) {
644
+ $scope.stage.baseLabel = $scope.baseLabelOptions[0];
645
+ }
646
+ $scope.viewState.loading = false;
647
+ });
648
+ }
649
+ function deleteEmptyProperties() {
650
+ _.forOwn($scope.stage, function(val, key) {
651
+ if (val === "") {
652
+ delete $scope.stage[key];
653
+ }
654
+ });
655
+ }
656
+ $scope.$watch("stage", deleteEmptyProperties, true);
657
+ initialize();
658
+ }
659
+ ]);
660
+ window.angular.module("ng").run(["$templateCache", function(templateCache) {
661
+ templateCache.put("docker/src/pipeline/stages/bake/bakeStage.html", `<div ng-controller="dockerBakeStageCtrl as bakeStageCtrl">
662
+ <stage-config-field label="Package" help-key="pipeline.config.bake.package">
663
+ <input type="text" class="form-control input-sm" ng-model="stage.package" />
664
+ </stage-config-field>
665
+ <stage-config-field label="Organization" help-key="pipeline.config.docker.bake.organization">
666
+ <input type="text" class="form-control input-sm" ng-model="stage.organization" />
667
+ </stage-config-field>
668
+ <stage-config-field label="Image Name" help-key="pipeline.config.docker.bake.targetImage">
669
+ <input type="text" class="form-control input-sm" ng-model="stage.ami_name" />
670
+ </stage-config-field>
671
+ <stage-config-field label="Image tag" help-key="pipeline.config.docker.bake.targetImageTag">
672
+ <input type="text" class="form-control input-sm" ng-model="stage.extendedAttributes['docker_target_image_tag']" />
673
+ </stage-config-field>
674
+ <stage-config-field label="Base OS">
675
+ <bake-stage-choose-os model="stage.baseOs" base-os-options="baseOsOptions"></bake-stage-choose-os>
676
+ </stage-config-field>
677
+
678
+ <stage-config-field label="Base Label">
679
+ <label class="radio-inline" ng-repeat="baseLabel in baseLabelOptions">
680
+ <input type="radio" ng-model="stage.baseLabel" ng-value="baseLabel" />
681
+ {{baseLabel}}
682
+ </label>
683
+ </stage-config-field>
684
+ <stage-config-field label="Rebake">
685
+ <div class="checkbox" style="margin-bottom: 0">
686
+ <label>
687
+ <input type="checkbox" ng-model="stage.rebake" />
688
+ Rebake image without regard to the status of any existing bake
689
+ </label>
690
+ </div>
691
+ </stage-config-field>
692
+ </div>
693
+ `);
694
+ }]);
695
+ window.angular.module("ng").run(["$templateCache", function(templateCache) {
696
+ templateCache.put("docker/src/pipeline/stages/bake/bakeExecutionDetails.html", `<div ng-controller="dockerBakeExecutionDetailsCtrl">
697
+ <execution-details-section-nav sections="configSections"></execution-details-section-nav>
698
+ <div class="step-section-details" ng-if="detailsSection === 'bakeConfig'">
699
+ <div class="row">
700
+ <div class="col-md-6">
701
+ <dl class="dl-narrow dl-horizontal">
702
+ <dt if-multiple-providers>Provider</dt>
703
+ <dd if-multiple-providers>Docker</dd>
704
+ <dt>Organization</dt>
705
+ <dd>{{stage.context.organization}}</dd>
706
+ <dt>Image Name</dt>
707
+ <dd>{{stage.context.ami_name}}</dd>
708
+ <dt>Image Tag</dt>
709
+ <dd>{{stage.context.extendedAttributes['docker_target_image_tag']}}</dd>
710
+ <dt>Image</dt>
711
+ <dd>{{stage.context.ami}}</dd>
712
+ </dl>
713
+ </div>
714
+ <div class="col-md-6">
715
+ <dl class="dl-narrow dl-horizontal">
716
+ <dt>Base OS</dt>
717
+ <dd>{{stage.context.baseOs}}</dd>
718
+ <dt>Region</dt>
719
+ <dd>{{stage.context.region}}</dd>
720
+ <dt>Package</dt>
721
+ <dd>{{stage.context.package}}</dd>
722
+ <dt>Label</dt>
723
+ <dd>{{stage.context.baseLabel}}</dd>
724
+ </dl>
725
+ </div>
726
+ </div>
727
+ <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>
728
+
729
+ <div class="row" ng-if="stage.context.region && stage.context.status.resourceId">
730
+ <div class="col-md-12">
731
+ <div class="alert alert-{{stage.isFailed ? 'danger' : 'info'}}">
732
+ <a target="_blank" href="{{ bakeryDetailUrl(stage) }}"> View Bakery Details </a>
733
+ </div>
734
+ </div>
735
+ </div>
736
+ </div>
737
+ <div class="step-section-details" ng-if="detailsSection === 'taskStatus'">
738
+ <div class="row">
739
+ <execution-step-details item="stage"></execution-step-details>
740
+ </div>
741
+ </div>
742
+ </div>
743
+ `);
744
+ }]);
745
+
746
+ function DockerTriggerConfig(props) {
747
+ const { formik } = props;
748
+ const trigger = formik.values;
749
+ const dockerChanged = (changes) => {
750
+ const { imageId, ...rest } = changes;
751
+ props.triggerUpdated(rest);
752
+ };
753
+ return /* @__PURE__ */ React.createElement("div", {
754
+ className: "form-horizontal"
755
+ }, /* @__PURE__ */ React.createElement(DockerImageAndTagSelector, {
756
+ allowManualDefinition: false,
757
+ specifyTagByRegex: true,
758
+ account: trigger.account,
759
+ organization: trigger.organization,
760
+ registry: trigger.registry,
761
+ repository: trigger.repository,
762
+ tag: trigger.tag,
763
+ showRegistry: true,
764
+ onChange: dockerChanged,
765
+ showDigest: false
766
+ }));
767
+ }
768
+
769
+ const lookupTypeOptions = [
770
+ { value: "digest", label: "Digest" },
771
+ { value: "tag", label: "Tag" }
772
+ ];
773
+ class DockerTriggerTemplate extends React.Component {
774
+ constructor(props) {
775
+ super(props);
776
+ this.queryStream = new Subject();
777
+ this.handleQuery = () => {
778
+ const trigger = this.props.command.trigger;
779
+ return from(DockerImageReader.findTags({
780
+ provider: "dockerRegistry",
781
+ account: trigger.account,
782
+ repository: trigger.repository
783
+ }));
784
+ };
785
+ this.lookupTypeChanged = (o) => {
786
+ const newType = o.value;
787
+ this.updateArtifact(this.props.command, newType === "tag" ? this.state.selectedTag : this.state.digest);
788
+ this.setState({ lookupType: newType });
789
+ };
790
+ this.updateSelectedTag = (tag) => {
791
+ this.updateArtifact(this.props.command, tag);
792
+ this.setState({ selectedTag: tag });
793
+ this.props.command.triggerInvalid = false;
794
+ };
795
+ this.updateDigest = (digest) => {
796
+ this.updateArtifact(this.props.command, digest);
797
+ this.setState({ digest });
798
+ };
799
+ this.tagLoadSuccess = (tags) => {
800
+ const { command } = this.props;
801
+ const trigger = command.trigger;
802
+ const newState = {};
803
+ newState.tags = tags || [];
804
+ const defaultSelection = newState.tags.find((t) => t === trigger.tag);
805
+ if (defaultSelection) {
806
+ newState.selectedTag = defaultSelection;
807
+ this.updateSelectedTag(defaultSelection);
808
+ }
809
+ newState.tagsLoading = false;
810
+ this.setState(newState);
811
+ };
812
+ this.tagLoadFailure = () => {
813
+ this.setState({
814
+ tagsLoading: false,
815
+ loadError: true
816
+ });
817
+ };
818
+ this.initialize = () => {
819
+ const { command } = this.props;
820
+ this.props.updateCommand("triggerInvalid", true);
821
+ this.props.updateCommand("extraFields", {
822
+ tag: get(command, "extraFields.tag", ""),
823
+ artifacts: get(command, "extraFields.artifacts", "")
824
+ });
825
+ if (this.subscription) {
826
+ this.subscription.unsubscribe();
827
+ }
828
+ if (command.trigger.type !== "docker") {
829
+ return;
830
+ }
831
+ this.subscription = this.queryStream.pipe(debounceTime(250), switchMap(this.handleQuery)).subscribe(this.tagLoadSuccess, this.tagLoadFailure);
832
+ this.searchTags();
833
+ };
834
+ this.searchTags = (query = "") => {
835
+ this.setState({ tags: [`<span>Finding tags${query && ` matching ${query}`}...</span>`] });
836
+ this.queryStream.next();
837
+ };
838
+ this.state = {
839
+ digest: "",
840
+ tags: [],
841
+ tagsLoading: true,
842
+ loadError: false,
843
+ lookupType: "tag",
844
+ selectedTag: ""
845
+ };
846
+ }
847
+ static formatLabel(trigger) {
848
+ return $q.when(`(Docker Registry) ${trigger.account ? trigger.account + ":" : ""} ${trigger.repository || ""}`);
849
+ }
850
+ updateArtifact(command, tagOrDigest) {
851
+ this.props.updateCommand("extraFields.tag", tagOrDigest);
852
+ const trigger = command.trigger;
853
+ if (trigger && trigger.repository) {
854
+ let imageName = "";
855
+ if (trigger.registry) {
856
+ imageName += trigger.registry + "/";
857
+ }
858
+ imageName += trigger.repository;
859
+ let imageReference = "";
860
+ if (this.state.lookupType === "digest") {
861
+ imageReference = `${imageName}@${tagOrDigest}`;
862
+ } else {
863
+ imageReference = `${imageName}:${tagOrDigest}`;
864
+ }
865
+ this.props.updateCommand("extraFields.artifacts", [
866
+ {
867
+ type: "docker/image",
868
+ name: imageName,
869
+ version: tagOrDigest,
870
+ reference: imageReference
871
+ }
872
+ ]);
873
+ }
874
+ }
875
+ componentDidMount() {
876
+ this.initialize();
877
+ }
878
+ componentWillUnmount() {
879
+ if (this.subscription) {
880
+ this.subscription.unsubscribe();
881
+ }
882
+ }
883
+ render() {
884
+ const { digest, tags, tagsLoading, loadError, selectedTag, lookupType } = this.state;
885
+ const options = tags.map((tag) => {
886
+ return { value: tag };
887
+ });
888
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
889
+ className: "form-group"
890
+ }, /* @__PURE__ */ React.createElement("div", {
891
+ className: "sm-label-right col-md-4"
892
+ }, "Type"), /* @__PURE__ */ React.createElement("div", {
893
+ className: "col-md-3"
894
+ }, /* @__PURE__ */ React.createElement(TetheredSelect, {
895
+ clearable: false,
896
+ value: lookupType,
897
+ options: lookupTypeOptions,
898
+ onChange: this.lookupTypeChanged
899
+ }))), lookupType === "tag" && /* @__PURE__ */ React.createElement("div", {
900
+ className: "form-group"
901
+ }, /* @__PURE__ */ React.createElement("label", {
902
+ className: "col-md-4 sm-label-right"
903
+ }, "Tag"), tagsLoading && /* @__PURE__ */ React.createElement("div", {
904
+ className: "col-md-6"
905
+ }, /* @__PURE__ */ React.createElement("div", {
906
+ className: "form-control-static text-center"
907
+ }, /* @__PURE__ */ React.createElement(Spinner, {
908
+ size: "small"
909
+ }))), loadError && /* @__PURE__ */ React.createElement("div", {
910
+ className: "col-md-6"
911
+ }, "Error loading tags!"), !tagsLoading && /* @__PURE__ */ React.createElement("div", {
912
+ className: "col-md-6"
913
+ }, tags.length === 0 && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
914
+ className: "form-control-static"
915
+ }, "No tags found")), tags.length > 0 && /* @__PURE__ */ React.createElement(TetheredSelect, {
916
+ options,
917
+ optionRenderer: (o) => /* @__PURE__ */ React.createElement("span", null, o.value),
918
+ clearable: false,
919
+ value: selectedTag,
920
+ valueRenderer: (o) => /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("strong", null, o.value)),
921
+ onChange: (o) => this.updateSelectedTag(o.value),
922
+ placeholder: "Search tags..."
923
+ }))), lookupType === "digest" && /* @__PURE__ */ React.createElement("div", {
924
+ className: "form-group"
925
+ }, /* @__PURE__ */ React.createElement("label", {
926
+ className: "col-md-4 sm-label-right"
927
+ }, "Digest ", /* @__PURE__ */ React.createElement(HelpField, {
928
+ id: "pipeline.config.docker.trigger.digest"
929
+ })), /* @__PURE__ */ React.createElement("div", {
930
+ className: "col-md-6"
931
+ }, /* @__PURE__ */ React.createElement("input", {
932
+ value: digest,
933
+ onChange: (e) => this.updateDigest(e.target.value),
934
+ className: "form-control input-sm",
935
+ required: true
936
+ }))));
937
+ }
938
+ }
939
+
940
+ const DockerTriggerExecutionStatus = (props) => {
941
+ const trigger = props.trigger;
942
+ return /* @__PURE__ */ React.createElement("li", null, trigger.repository, ":", trigger.tag);
943
+ };
944
+ Registry.pipeline.registerTrigger({
945
+ label: "Docker Registry",
946
+ description: "Executes the pipeline on an image update",
947
+ key: "docker",
948
+ component: DockerTriggerConfig,
949
+ manualExecutionComponent: DockerTriggerTemplate,
950
+ executionStatusComponent: DockerTriggerExecutionStatus,
951
+ executionTriggerLabel: () => "Docker Registry",
952
+ validators: [
953
+ {
954
+ type: "requiredField",
955
+ fieldName: "account",
956
+ message: "<strong>Registry</strong> is a required field for Docker Registry triggers."
957
+ },
958
+ {
959
+ type: "requiredField",
960
+ fieldName: "repository",
961
+ message: "<strong>Image</strong> is a required field for Docker Registry triggers."
962
+ },
963
+ {
964
+ type: "serviceAccountAccess",
965
+ preventSave: true,
966
+ message: `You do not have access to the service account configured in this pipeline's Docker Registry trigger.
967
+ You will not be able to save your edits to this pipeline.`
968
+ }
969
+ ]
970
+ });
971
+
972
+ const DOCKER_MODULE = "spinnaker.docker";
973
+ module(DOCKER_MODULE, [DOCKER_PIPELINE_STAGES_BAKE_DOCKERBAKESTAGE]);
974
+
975
+ export { DOCKER_MODULE, DockerImageAndTagSelector, DockerImageReader, DockerImageUtils };
2
976
  //# sourceMappingURL=index.js.map