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  }