github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/action.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Copyright 2016 Cloudbase Solutions 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package common 6 7 import ( 8 "github.com/juju/errors" 9 "github.com/juju/names/v5" 10 11 apiservererrors "github.com/juju/juju/apiserver/errors" 12 "github.com/juju/juju/apiserver/facade" 13 "github.com/juju/juju/rpc/params" 14 "github.com/juju/juju/state" 15 "github.com/juju/juju/state/watcher" 16 ) 17 18 // ParamsActionExecutionResultsToStateActionResults does exactly what 19 // the name implies. 20 func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) { 21 var status state.ActionStatus 22 switch arg.Status { 23 case params.ActionCancelled: 24 status = state.ActionCancelled 25 case params.ActionCompleted: 26 status = state.ActionCompleted 27 case params.ActionFailed: 28 status = state.ActionFailed 29 case params.ActionPending: 30 status = state.ActionPending 31 case params.ActionAborting: 32 status = state.ActionAborting 33 case params.ActionAborted: 34 status = state.ActionAborted 35 case params.ActionError: 36 status = state.ActionError 37 default: 38 return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status) 39 } 40 return state.ActionResults{ 41 Status: status, 42 Results: arg.Results, 43 Message: arg.Message, 44 }, nil 45 } 46 47 // TagToActionReceiverFn takes a tag string and tries to convert it to an 48 // ActionReceiver. It needs a findEntity function passed in that can search for the tags in state. 49 func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) { 50 return func(tag string) (state.ActionReceiver, error) { 51 receiverTag, err := names.ParseTag(tag) 52 if err != nil { 53 return nil, errors.NotValidf("%q", tag) 54 } 55 entity, err := findEntity(receiverTag) 56 if err != nil { 57 return nil, errors.NotFoundf("%q", receiverTag) 58 } 59 receiver, ok := entity.(state.ActionReceiver) 60 if !ok { 61 return nil, errors.NotImplementedf("action receiver interface on entity %s", tag) 62 } 63 return receiver, nil 64 } 65 } 66 67 // AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state 68 // and returns a function that can fetch an action from state by id and check the authorization. 69 func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) { 70 return func(tag string) (state.Action, error) { 71 actionTag, err := names.ParseActionTag(tag) 72 if err != nil { 73 return nil, errors.Trace(err) 74 } 75 action, err := getActionByTag(actionTag) 76 if err != nil { 77 return nil, errors.Trace(err) 78 } 79 receiverTag, err := names.ActionReceiverTag(action.Receiver()) 80 if err != nil { 81 return nil, errors.Trace(err) 82 } 83 if !canAccess(receiverTag) { 84 return nil, apiservererrors.ErrPerm 85 } 86 return action, nil 87 } 88 } 89 90 // BeginActions calls begin on every action passed in through args. 91 // It's a helper function currently used by the uniter and by machineactions 92 // It needs an actionFn that can fetch an action from state using it's id, that's usually created by AuthAndActionFromTagFn 93 func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults { 94 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))} 95 96 for i, arg := range args.Entities { 97 action, err := actionFn(arg.Tag) 98 if err != nil { 99 results.Results[i].Error = apiservererrors.ServerError(err) 100 continue 101 } 102 103 _, err = action.Begin() 104 if err != nil { 105 results.Results[i].Error = apiservererrors.ServerError(err) 106 continue 107 } 108 } 109 110 return results 111 } 112 113 // FinishActions saves the result of a completed Action. 114 // It's a helper function currently used by the uniter and by machineactions 115 // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn 116 func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults { 117 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))} 118 119 for i, arg := range args.Results { 120 action, err := actionFn(arg.ActionTag) 121 if err != nil { 122 results.Results[i].Error = apiservererrors.ServerError(err) 123 continue 124 } 125 actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg) 126 if err != nil { 127 results.Results[i].Error = apiservererrors.ServerError(err) 128 continue 129 } 130 131 _, err = action.Finish(actionResults) 132 if err != nil { 133 results.Results[i].Error = apiservererrors.ServerError(err) 134 continue 135 } 136 } 137 138 return results 139 } 140 141 // Actions returns the Actions by Tags passed in and ensures that the receiver asking for 142 // them is the same one that has the action. 143 // It's a helper function currently used by the uniter and by machineactions. 144 // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn 145 func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults { 146 results := params.ActionResults{ 147 Results: make([]params.ActionResult, len(args.Entities)), 148 } 149 150 for i, arg := range args.Entities { 151 action, err := actionFn(arg.Tag) 152 if err != nil { 153 results.Results[i].Error = apiservererrors.ServerError(err) 154 continue 155 } 156 if action.Status() != state.ActionPending { 157 results.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrActionNotAvailable) 158 continue 159 } 160 parallel := action.Parallel() 161 executionGroup := action.ExecutionGroup() 162 results.Results[i].Action = ¶ms.Action{ 163 Name: action.Name(), 164 Parameters: action.Parameters(), 165 Parallel: ¶llel, 166 ExecutionGroup: &executionGroup, 167 } 168 } 169 170 return results 171 } 172 173 // WatchOneActionReceiverNotifications returns a function for creating a 174 // watcher on all action notifications (action adds + changes) for one receiver. 175 // It needs a tagToActionReceiver function and a registerFunc to register 176 // resources. 177 // It's a helper function currently used by the uniter and by machineactions 178 func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r facade.Resource) string) func(names.Tag) (params.StringsWatchResult, error) { 179 return func(tag names.Tag) (params.StringsWatchResult, error) { 180 nothing := params.StringsWatchResult{} 181 receiver, err := tagToActionReceiver(tag.String()) 182 if err != nil { 183 return nothing, err 184 } 185 watch := receiver.WatchActionNotifications() 186 187 if changes, ok := <-watch.Changes(); ok { 188 return params.StringsWatchResult{ 189 StringsWatcherId: registerFunc(watch), 190 Changes: changes, 191 }, nil 192 } 193 return nothing, watcher.EnsureErr(watch) 194 } 195 } 196 197 // WatchPendingActionsForReceiver returns a function for creating a 198 // watcher on new pending Actions for one receiver. 199 // It needs a tagToActionReceiver function and a registerFunc to register 200 // resources. 201 // It's a helper function currently used by the uniter and by machineactions 202 func WatchPendingActionsForReceiver(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r facade.Resource) string) func(names.Tag) (params.StringsWatchResult, error) { 203 return func(tag names.Tag) (params.StringsWatchResult, error) { 204 nothing := params.StringsWatchResult{} 205 receiver, err := tagToActionReceiver(tag.String()) 206 if err != nil { 207 return nothing, err 208 } 209 watch := receiver.WatchPendingActionNotifications() 210 211 if changes, ok := <-watch.Changes(); ok { 212 return params.StringsWatchResult{ 213 StringsWatcherId: registerFunc(watch), 214 Changes: changes, 215 }, nil 216 } 217 return nothing, watcher.EnsureErr(watch) 218 } 219 } 220 221 // WatchActionNotifications returns a StringsWatcher for observing incoming actions towards an actionreceiver. 222 // It's a helper function currently used by the uniter and by machineactions 223 // canAccess is passed in by the respective caller to provide authorization. 224 // watchOne is usually a function created by WatchOneActionReceiverNotifications 225 func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults { 226 result := params.StringsWatchResults{ 227 Results: make([]params.StringsWatchResult, len(args.Entities)), 228 } 229 230 for i, entity := range args.Entities { 231 tag, err := names.ActionReceiverFromTag(entity.Tag) 232 if err != nil { 233 result.Results[i].Error = apiservererrors.ServerError(err) 234 continue 235 } 236 err = apiservererrors.ErrPerm 237 if canAccess(tag) { 238 result.Results[i], err = watchOne(tag) 239 } 240 result.Results[i].Error = apiservererrors.ServerError(err) 241 } 242 243 return result 244 } 245 246 // GetActionsFn declares the function type that returns a slice of 247 // state.Action and error, used to curry specific list functions. 248 type GetActionsFn func() ([]state.Action, error) 249 250 // ConvertActions takes a generic getActionsFn to obtain a slice 251 // of state.Action and then converts them to the API slice of 252 // params.ActionResult. 253 func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) { 254 items := []params.ActionResult{} 255 actions, err := fn() 256 if err != nil { 257 return items, err 258 } 259 for _, action := range actions { 260 if action == nil { 261 continue 262 } 263 items = append(items, MakeActionResult(ar.Tag(), action)) 264 } 265 return items, nil 266 } 267 268 // MakeActionResult does the actual type conversion from state.Action 269 // to params.ActionResult. 270 func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult { 271 output, message := action.Results() 272 parallel := action.Parallel() 273 executionGroup := action.ExecutionGroup() 274 result := params.ActionResult{ 275 Action: ¶ms.Action{ 276 Receiver: actionReceiverTag.String(), 277 Tag: action.ActionTag().String(), 278 Name: action.Name(), 279 Parameters: action.Parameters(), 280 Parallel: ¶llel, 281 ExecutionGroup: &executionGroup, 282 }, 283 Status: string(action.Status()), 284 Message: message, 285 Output: output, 286 Enqueued: action.Enqueued(), 287 Started: action.Started(), 288 Completed: action.Completed(), 289 } 290 for _, m := range action.Messages() { 291 result.Log = append(result.Log, params.ActionMessage{ 292 Timestamp: m.Timestamp(), 293 Message: m.Message(), 294 }) 295 } 296 297 return result 298 }