get.porter.sh/porter@v1.3.0/pkg/storage/run.go (about) 1 package storage 2 3 import ( 4 "crypto/sha256" 5 "encoding/json" 6 "fmt" 7 "time" 8 9 "get.porter.sh/porter/pkg/cnab" 10 "github.com/cnabio/cnab-go/bundle" 11 ) 12 13 var _ Document = Run{} 14 var _ json.Marshaler = Run{} 15 var _ json.Unmarshaler = &Run{} 16 17 // Run represents the execution of an installation's bundle. It contains both the 18 // instructions used by Porter to run the bundle, and additional status/audit 19 // fields so users can keep track of how the bundle was run. 20 type Run struct { 21 // SchemaVersion of the document. 22 SchemaVersion cnab.SchemaVersion `json:"schemaVersion"` 23 24 // ID of the Run. 25 ID string `json:"_id"` 26 27 // Created timestamp of the Run. 28 Created time.Time `json:"created"` 29 30 // Modified timestamp of the Run, set when we resolve run parameters just-in-time. 31 // A run can be created ahead of time as Pending and not have its parameters resolved until much later. 32 Modified time.Time `json:"modified"` 33 34 // Namespace of the installation. 35 Namespace string `json:"namespace"` 36 37 // Installation name. 38 Installation string `json:"installation"` 39 40 // Revision of the installation. 41 Revision string `json:"revision"` 42 43 // Action executed against the installation. 44 Action string `json:"action"` 45 46 // Bundle is the definition of the bundle. 47 // Bundle has custom marshal logic in MarshalJson. 48 Bundle bundle.Bundle `json:"-"` 49 50 // BundleReference is the canonical reference to the bundle used in the action. 51 BundleReference string `json:"bundleReference"` 52 53 // BundleDigest is the digest of the bundle. 54 // TODO(carolynvs): populate this 55 BundleDigest string `json:"bundleDigest"` 56 57 // ParameterOverrides are the key/value parameter overrides (taking precedence over 58 // parameters specified in a parameter set) specified during the run. 59 // This is a status/audit field and is not used to resolve parameters for a Run. 60 ParameterOverrides ParameterSet `json:"parameterOverrides,omitempty"` 61 62 // CredentialSets is a list of the credential set names used during the run. 63 // This is a status/audit field and is not used to resolve credentials for a Run. 64 CredentialSets []string `json:"credentialSets,omitempty"` 65 66 // ParameterSets is the list of parameter set names used during the run. 67 // This is a status/audit field and is not used to resolve parameters for a Run. 68 ParameterSets []string `json:"parameterSets,omitempty"` 69 70 // Parameters is the full set of parameters that should be resolved just-in-time 71 // (JIT) before executing the bundle. This includes internal parameters, 72 // parameter sources, values from parameter sets, etc. These should be a "clean" 73 // set of parameters that have sensitive values persisted in secrets using the 74 // Sanitizer. 75 // After the parameters are resolved, this structure holds (but does not marshal) 76 // the resolved values, in addition to the mapping strategy. 77 Parameters ParameterSet `json:"parameters,omitempty"` 78 79 // Custom extension data applicable to a given runtime. 80 // TODO(carolynvs): remove custom and populate it in ToCNAB 81 Custom interface{} `json:"custom"` 82 83 // ParametersDigest is a hash or digest of the final set of parameters, which allows us to 84 // quickly determine if the parameters have changed without requiring that they 85 // are re-resolved. The value should contain the hash type, e.g. sha256:abc123... 86 // This is a status/audit field and is not used to resolve parameters for a Run. 87 ParametersDigest string `json:"parametersDigest,omitempty"` 88 89 // Credentials is the full set of credentials that should be resolved 90 // just-in-time (JIT) before executing the bundle. These should be a "clean" set 91 // of parameters that have sensitive values persisted in secrets using the 92 // Sanitizer. 93 Credentials CredentialSet `json:"credentials,omitempty"` 94 95 // CredentialsDigest is a hash or digest of the final set of credentials, which allows us to 96 // quickly determine if the credentials have changed without requiring that they 97 // are re-resolved. The value should contain the hash type, e.g. sha256:abc123... 98 // This is a status/audit field and is not used to resolve credentials for a Run. 99 CredentialsDigest string `json:"credentialsDigest,omitempty"` 100 } 101 102 // rawRun is an alias for Run that does not have a json marshal functions defined, 103 // so it's safe to marshal without causing infinite recursive calls. 104 // See http://choly.ca/post/go-json-marshalling/ 105 type rawRun Run 106 107 // mongoRun is the representation of the Run that we store in mongodb. 108 type mongoRun struct { 109 rawRun 110 111 // Bundle is stored in mongo as a string because it has fields that are prefixed with a $, such as $id and $comment. 112 // It overrides Run.Bundle. 113 Bundle BundleDocument `json:"bundle"` 114 } 115 116 // MarshalJSON converts the run to its storage representation in mongo. 117 func (r Run) MarshalJSON() ([]byte, error) { 118 data, err := json.Marshal(mongoRun{ 119 rawRun: rawRun(r), 120 Bundle: BundleDocument(r.Bundle), 121 }) 122 if err != nil { 123 return nil, fmt.Errorf("error marshaling Run into its storage representation: %w", err) 124 } 125 return data, nil 126 } 127 128 // UnmarshalJSON converts the run to its storage representation in mongo. 129 func (r *Run) UnmarshalJSON(data []byte) error { 130 var mr mongoRun 131 if err := json.Unmarshal(data, &mr); err != nil { 132 return fmt.Errorf("error unmarshaling Run from its storage representation: %w", err) 133 } 134 135 mr.rawRun.Bundle = bundle.Bundle(mr.Bundle) 136 *r = Run(mr.rawRun) 137 return nil 138 } 139 140 func (r Run) DefaultDocumentFilter() map[string]interface{} { 141 return map[string]interface{}{"_id": r.ID} 142 } 143 144 // NewRun creates a run with default values initialized. 145 func NewRun(namespace string, installation string) Run { 146 return Run{ 147 SchemaVersion: DefaultInstallationSchemaVersion, 148 ID: cnab.NewULID(), 149 Revision: cnab.NewULID(), 150 Created: time.Now(), 151 Modified: time.Now(), 152 Namespace: namespace, 153 Installation: installation, 154 Parameters: NewInternalParameterSet(namespace, installation), 155 } 156 } 157 158 // ShouldRecord the current run in the Installation history. 159 // Runs are only recorded for actions that modify the bundle resources, 160 // or for stateful actions. Stateless actions do not require an existing 161 // installation or credentials, and are for actions such as documentation, dry-run, etc. 162 func (r Run) ShouldRecord() bool { 163 // Assume all actions modify bundle resources, and should be recorded. 164 stateful := true 165 modifies := true 166 hasOutput := false 167 168 if action, err := r.Bundle.GetAction(r.Action); err == nil { 169 modifies = action.Modifies 170 stateful = !action.Stateless 171 } 172 173 bun := cnab.ExtendedBundle{Bundle: r.Bundle} 174 for _, outputDef := range r.Bundle.Outputs { 175 if outputDef.AppliesTo(r.Action) && !bun.IsInternalOutput(outputDef.Definition) { 176 hasOutput = true 177 break 178 } 179 } 180 181 return modifies || stateful || hasOutput 182 } 183 184 // ToCNAB associated with the Run. 185 func (r Run) ToCNAB() cnab.Claim { 186 return cnab.Claim{ 187 // CNAB doesn't have the concept of namespace, so we smoosh them together to make a unique name 188 SchemaVersion: cnab.ClaimSchemaVersion(), 189 ID: r.ID, 190 Installation: r.Namespace + "/" + r.Installation, 191 Revision: r.Revision, 192 Created: r.Created, 193 Action: r.Action, 194 Bundle: r.Bundle, 195 BundleReference: r.BundleReference, 196 Parameters: r.TypedParameterValues(), 197 Custom: r.Custom, 198 } 199 } 200 201 // TypedParameterValues returns parameters values that have been converted to 202 // its typed value based on its bundle definition. 203 func (r Run) TypedParameterValues() map[string]interface{} { 204 bun := cnab.NewBundle(r.Bundle) 205 value := make(map[string]interface{}) 206 207 for _, param := range r.Parameters.Parameters { 208 v, err := bun.ConvertParameterValue(param.Name, param.ResolvedValue) 209 if err != nil { 210 value[param.Name] = param.ResolvedValue 211 continue 212 } 213 def, ok := bun.Definitions[param.Name] 214 if !ok { 215 value[param.Name] = v 216 continue 217 } 218 if bun.IsFileType(def) && v == "" { 219 v = nil 220 } 221 222 value[param.Name] = v 223 } 224 225 return value 226 227 } 228 229 // SetParametersDigest records the hash of the resolved parameters, so we can 230 // quickly tell if the parameters between runs were different without 231 // re-resolving them. 232 func (r *Run) SetParametersDigest() error { 233 // Calculate a hash of the resolved parameters 234 paramB, err := json.Marshal(r.Parameters.Parameters) 235 if err != nil { 236 r.ParametersDigest = "" 237 return fmt.Errorf("error calculating the digest of the run parameters: %w", err) 238 } 239 240 r.ParametersDigest = fmt.Sprintf("sha256:%x", sha256.Sum256(paramB)) 241 return nil 242 } 243 244 // SetCredentialsDigest records the hash of the resolved credentials, so we can 245 // quickly tell if the parameters between runs were different without 246 // re-resolving them. 247 func (r *Run) SetCredentialsDigest() error { 248 // Calculate a hash of the resolved credentials 249 credB, err := json.Marshal(r.Credentials.Credentials) 250 if err != nil { 251 r.CredentialsDigest = "" 252 return fmt.Errorf("error calculating the digest of the run credentials: %w", err) 253 } 254 255 r.CredentialsDigest = fmt.Sprintf("sha256:%x", sha256.Sum256(credB)) 256 return nil 257 } 258 259 // NewRun creates a result for the current Run. 260 func (r Run) NewResult(status string) Result { 261 result := NewResult() 262 result.RunID = r.ID 263 result.Namespace = r.Namespace 264 result.Installation = r.Installation 265 result.Status = status 266 return result 267 } 268 269 // NewResultFrom creates a result from the output of a CNAB run. 270 func (r Run) NewResultFrom(cnabResult cnab.Result) Result { 271 return Result{ 272 SchemaVersion: DefaultInstallationSchemaVersion, 273 ID: cnabResult.ID, 274 Namespace: r.Namespace, 275 Installation: r.Installation, 276 RunID: r.ID, 277 Created: cnabResult.Created, 278 Status: cnabResult.Status, 279 Message: cnabResult.Message, 280 OutputMetadata: cnabResult.OutputMetadata, 281 Custom: cnabResult.Custom, 282 } 283 }