github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/migrations/remoteapplications.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrations 5 6 import ( 7 "github.com/juju/charm/v12" 8 "github.com/juju/description/v5" 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 ) 12 13 // MigrationRemoteApplication is an in-place representation of the 14 // state.RemoteApplication 15 type MigrationRemoteApplication interface { 16 Tag() names.Tag 17 OfferUUID() string 18 URL() (string, bool) 19 SourceModel() names.ModelTag 20 IsConsumerProxy() bool 21 Endpoints() ([]MigrationRemoteEndpoint, error) 22 Bindings() map[string]string 23 Spaces() []MigrationRemoteSpace 24 GlobalKey() string 25 Macaroon() string 26 ConsumeVersion() int 27 } 28 29 // MigrationRemoteEndpoint is an in-place representation of the state.Endpoint 30 type MigrationRemoteEndpoint struct { 31 Name string 32 Role charm.RelationRole 33 Interface string 34 } 35 36 // MigrationRemoteSpace is an in-place representation of the state.RemoteSpace 37 type MigrationRemoteSpace struct { 38 CloudType string 39 Name string 40 ProviderId string 41 ProviderAttributes map[string]interface{} 42 Subnets []MigrationRemoteSubnet 43 } 44 45 // MigrationRemoteSubnet is an in-place representation of the state.RemoteSubnet 46 type MigrationRemoteSubnet struct { 47 CIDR string 48 ProviderId string 49 VLANTag int 50 AvailabilityZones []string 51 ProviderSpaceId string 52 ProviderNetworkId string 53 } 54 55 // AllRemoteApplicationSource defines an in-place usage for reading all the 56 // remote application. 57 type AllRemoteApplicationSource interface { 58 AllRemoteApplications() ([]MigrationRemoteApplication, error) 59 } 60 61 // StatusSource defines an in-place usage for reading in the status for a given 62 // entity. 63 type StatusSource interface { 64 StatusArgs(string) (description.StatusArgs, error) 65 } 66 67 // RemoteApplicationSource composes all the interfaces to create a remote 68 // application. 69 type RemoteApplicationSource interface { 70 AllRemoteApplicationSource 71 StatusSource 72 } 73 74 // RemoteApplicationModel defines an in-place usage for adding a remote entity 75 // to a model. 76 type RemoteApplicationModel interface { 77 AddRemoteApplication(description.RemoteApplicationArgs) description.RemoteApplication 78 } 79 80 // ExportRemoteApplications describes a way to execute a migration for exporting 81 // remote entities. 82 type ExportRemoteApplications struct{} 83 84 // Execute the migration of the remote entities using typed interfaces, to 85 // ensure we don't loose any type safety. 86 // This doesn't conform to an interface because go doesn't have generics, but 87 // when this does arrive this would be an excellent place to use them. 88 func (m ExportRemoteApplications) Execute(src RemoteApplicationSource, dst RemoteApplicationModel) error { 89 remoteApps, err := src.AllRemoteApplications() 90 if err != nil { 91 return errors.Trace(err) 92 } 93 94 for _, remoteApp := range remoteApps { 95 if err := m.addRemoteApplication(src, dst, remoteApp); err != nil { 96 return errors.Trace(err) 97 } 98 } 99 return nil 100 } 101 102 func (m ExportRemoteApplications) addRemoteApplication(src RemoteApplicationSource, dst RemoteApplicationModel, app MigrationRemoteApplication) error { 103 // Note the ignore case is not an error, but a bool indicating if it's valid 104 // or not. For this scenario, we're happy to ignore that situation. 105 url, _ := app.URL() 106 107 args := description.RemoteApplicationArgs{ 108 Tag: app.Tag().(names.ApplicationTag), 109 OfferUUID: app.OfferUUID(), 110 URL: url, 111 SourceModel: app.SourceModel(), 112 IsConsumerProxy: app.IsConsumerProxy(), 113 Bindings: app.Bindings(), 114 Macaroon: app.Macaroon(), 115 ConsumeVersion: app.ConsumeVersion(), 116 } 117 descApp := dst.AddRemoteApplication(args) 118 119 status, err := src.StatusArgs(app.GlobalKey()) 120 if err != nil && !errors.IsNotFound(err) { 121 return errors.Trace(err) 122 } 123 // Not all remote applications have status. 124 if err == nil { 125 descApp.SetStatus(status) 126 } 127 128 endpoints, err := app.Endpoints() 129 if err != nil { 130 return errors.Trace(err) 131 } 132 for _, ep := range endpoints { 133 descApp.AddEndpoint(description.RemoteEndpointArgs{ 134 Name: ep.Name, 135 Role: string(ep.Role), 136 Interface: ep.Interface, 137 }) 138 } 139 for _, space := range app.Spaces() { 140 m.addRemoteSpace(descApp, space) 141 } 142 return nil 143 } 144 145 func (m ExportRemoteApplications) addRemoteSpace(descApp description.RemoteApplication, space MigrationRemoteSpace) { 146 descSpace := descApp.AddSpace(description.RemoteSpaceArgs{ 147 CloudType: space.CloudType, 148 Name: space.Name, 149 ProviderId: space.ProviderId, 150 ProviderAttributes: space.ProviderAttributes, 151 }) 152 for _, subnet := range space.Subnets { 153 descSpace.AddSubnet(description.SubnetArgs{ 154 CIDR: subnet.CIDR, 155 ProviderId: subnet.ProviderId, 156 VLANTag: subnet.VLANTag, 157 AvailabilityZones: subnet.AvailabilityZones, 158 ProviderSpaceId: subnet.ProviderSpaceId, 159 ProviderNetworkId: subnet.ProviderNetworkId, 160 }) 161 } 162 }